* Add enclair_db option to UserDB.pm. Allows logging of enclair password
[interchange.git] / lib / Vend / Table / Editor.pm
1 # Vend::Table::Editor - Swiss-army-knife table editor for Interchange
2 #
3 # $Id: Editor.pm,v 1.93 2009-03-20 18:59:35 mheins Exp $
4 #
5 # Copyright (C) 2002-2008 Interchange Development Group
6 # Copyright (C) 2002 Mike Heins <mike@perusion.net>
7 #
8 # This program was originally based on Vend 0.2 and 0.3
9 # Copyright 1995 by Andrew M. Wilcox <amw@wilcoxsolutions.com>
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public
22 # License along with this program; if not, write to the Free
23 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
24 # MA  02110-1301  USA.
25
26 package Vend::Table::Editor;
27
28 use vars qw($VERSION);
29 $VERSION = substr(q$Revision: 1.93 $, 10);
30
31 use Vend::Util;
32 use Vend::Interpolate;
33 use Vend::Data;
34 use Exporter;
35 @EXPORT_OK = qw/meta_record expand_values tabbed_display display/;
36 use strict;
37 no warnings qw(uninitialized numeric);
38
39 =head1 NAME
40
41 Vend::Table::Editor -- Interchange do-all HTML table editor
42
43 =head1 SYNOPSIS
44
45 [table-editor OPTIONS] 
46
47 [table-editor OPTIONS] TEMPLATE [/table-editor]
48
49 =head1 DESCRIPTION
50
51 The [table-editor] tag produces an HTML form that edits a database
52 table or collects values for a "wizard". It is extremely configurable
53 as to display and characteristics of the widgets used to collect the
54 input.
55
56 The widget types are based on the Interchange C<[display ...]> UserTag,
57 which in turn is heavily based on the ITL core C<[accessories ...]> tag.
58
59 The C<simplest> form of C<[table-editor]> is:
60
61         [table-editor table=foo]
62
63 A page which contains only that tag will edit the table C<foo>, where
64 C<foo> is the name of an Interchange table to edit. If no C<foo> table
65 is C<defined>, then nothing will be displayed.
66
67 If the C<mv_metadata> entry "foo" is present, it is used as the
68 definition for table display, including the fields to edit and labels
69 for sections of the form. If C<ui_data_fields> is defined, this
70 cancels fetch of the view and any breaks and labels must be
71 defined with C<ui_break_before> and C<ui_break_before_label>. More
72 on the view concept later.
73
74 A simple "wizard" can be made with:
75
76         [table-editor
77                         wizard=1
78                         ui_wizard_fields="foo bar"
79                         mv_nextpage=wizard2
80                         mv_prevpage=wizard_intro
81                         ]
82
83 The purpose of a "wizard" is to collect values from the user and
84 place them in the $Values array. A next page value (option mv_nextpage)
85 must be defined to give a destination; if mv_prevpage is defined then
86 a "Back" button is presented to allow paging backward in the wizard.
87
88 =cut
89
90 my $Tag = new Vend::Tags;
91
92 my $Trailer;
93
94 use vars qw/%Display_type %Display_options %Style_sheet/;
95
96 %Display_options = (
97         three_column => sub {
98                 my $opt = shift;
99                 $opt->{cell_span} = 3;
100                 return;
101         },
102         nospace => sub {
103                 my $opt = shift;
104                 $opt->{break_cell_first_style} ||= 'border-top: 1px solid #999999';
105                 return;
106         },
107         adjust_width => sub {
108                 my $opt = shift;
109                 $opt->{adjust_cell_class} ||= $opt->{widget_cell_class};
110                 if($opt->{table_width} and $opt->{table_width} =~ /^\s*(\d+)(\w*)\s*$/) {
111                         my $wid = $1;
112                         my $type = $2 || '';
113                         $opt->{help_cell_style} ||= 'width: ' . int($wid * .35) . $type;
114                 }
115                 else {
116                         $opt->{help_cell_style} ||= 'width: 400';
117                 }
118                 return;
119         },
120 );
121
122 %Display_type = (
123         default => sub {
124                 my $opt = shift;
125                 my $thing = <<EOF;
126    <td$opt->{label_cell_extra}> 
127      {BLABEL}{LABEL}{ELABEL}{META_STRING}
128    </td>
129    <td$opt->{data_cell_extra}\{COLSPAN}>
130      <table cellspacing="0" cellmargin="0" width="100%">
131        <tr> 
132          <td$opt->{widget_cell_extra}>
133            {WIDGET}
134          </td>
135          <td$opt->{help_cell_extra}>{TKEY}{HELP?}<i>{HELP}</i>{/HELP?}{HELP_URL?}<br><a href="{HELP_URL}">$opt->{help_anchor}</a>{/HELP_URL?}</td>
136        </tr>
137      </table>
138    </td>
139 EOF
140                 chomp $thing;
141                 return $thing;
142         },
143         blank => sub {
144                 my $opt = shift;
145                 my $thing = <<EOF;
146 EOF
147                 chomp $thing;
148                 return $thing;
149
150         },
151         nospace => sub {
152                 my $opt = shift;
153                 my $span = shift;
154                 $opt->{break_template} ||= <<EOF;
155 <tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}\{FIRST?} style="$opt->{break_cell_first_style}"{/FIRST?}>{ROW}</td></tr>
156 EOF
157                 my $thing = <<EOF;
158    <td$opt->{label_cell_extra}> 
159      {BLABEL}{LABEL}{ELABEL}
160    </td>
161    <td$opt->{data_cell_extra}\{COLSPAN}>
162      <table cellspacing="0" cellmargin="0" width="100%">
163        <tr> 
164          <td$opt->{widget_cell_extra}>
165            {WIDGET}
166          </td>
167          <td$opt->{help_cell_extra}>{TKEY}{HELP?}<i>{HELP}</i>{/HELP?}{HELP_URL?}<br><a href="{HELP_URL}">$opt->{help_anchor}</a>{/HELP_URL?}</td>
168          <td align="right">{META_STRING}</td>
169        </tr>
170      </table>
171    </td>
172 EOF
173                 chomp $thing;
174                 return $thing;
175
176         },
177         text_js_help => sub {
178                 my $opt = shift;
179                 my $thing = <<EOF;
180    <td$opt->{label_cell_extra}> 
181      {BLABEL}{LABEL}{ELABEL}
182    </td>
183    <td$opt->{data_cell_extra}\{COLSPAN} nowrap>{WIDGET}{HELP_EITHER?}&nbsp;<a href="{HELP_URL?}{HELP_URL}{/HELP_URL?}{HELP_URL:}javascript:alert('{HELP}'); void(0){/HELP_URL:}" title="{HELP}">$opt->{help_anchor}</a>{/HELP_EITHER?}&nbsp;{META_URL?}<a href="{META_URL}">$opt->{meta_anchor}</a>{/META_URL?}
184    </td>
185 EOF
186                 chomp $thing;
187                 return $thing;
188         },
189         three_column => sub {
190                 my $opt = shift;
191                 my $thing = <<EOF;
192    <td$opt->{label_cell_extra}> 
193      {BLABEL}{LABEL}{ELABEL}
194    </td>
195    <td$opt->{data_cell_extra}\{COLSPAN} nowrap>
196         {WIDGET}
197    </td>
198    <td>{HELP_EITHER?}&nbsp;<a href="{HELP_URL}" title="{HELP}">$opt->{help_anchor}</a>{/HELP_EITHER?}&nbsp;{META_URL?}<a href="{META_URL}">$opt->{meta_anchor}</a>{/META_URL?}
199    </td>
200 EOF
201                 chomp $thing;
202                 return $thing;
203         },
204         simple_row => sub {
205 #::logDebug("calling simple_row display");
206                 my $opt = shift;
207                 my $thing = <<EOF;
208    <td$opt->{label_cell_extra}> 
209      {BLABEL}{LABEL}{ELABEL}
210    </td>
211    <td$opt->{data_cell_extra}\{COLSPAN} nowrap>{WIDGET}{HELP_EITHER?}&nbsp;<a href="{HELP_URL}" title="{HELP}">$opt->{help_anchor}</a>{/HELP_EITHER?}&nbsp;{META_URL?}<a href="{META_URL}">$opt->{meta_anchor}</a>{/META_URL?}
212    </td>
213 EOF
214                 chomp $thing;
215                 return $thing;
216         },
217         over_under => sub {
218                 my $opt = shift;
219                 my $thing = <<EOF;
220 {HELP?}
221         <td colspan="2"$opt->{help_cell_extra}>
222                 {HELP}
223         </td>
224 </tr>
225 <tr>
226 {/HELP?}        <td colspan="2"$opt->{label_cell_extra}>
227                 {LABEL}
228         </td>
229 </tr>
230 <tr>
231         <td colspan="2"$opt->{widget_cell_extra}>
232                 {WIDGET}
233         </td>
234 EOF
235                 chomp $thing;
236                 return $thing;
237         },
238         adjust_width => sub {
239                 my $opt = shift;
240                 my $span = shift;
241                 $opt->{break_template} ||= <<EOF;
242 $opt->{spacer_row}
243 <tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}>{ROW}</td></tr>
244 EOF
245                 my $thing = <<EOF;
246    <td$opt->{label_cell_extra}> 
247      {BLABEL}{LABEL}{ELABEL}
248    </td>
249    <td$opt->{data_cell_extra}\{COLSPAN}>
250      <table cellspacing="0" cellmargin="0" width="100%">
251        <tr> 
252          <td$opt->{widget_cell_extra}>
253            {WIDGET}
254          </td>
255          <td$opt->{help_cell_extra}>{TKEY}{HELP?}{HELP}{/HELP?}{HELP:}&nbsp;{/HELP:}{HELP_URL?}<br><a href="{HELP_URL}">$opt->{help_anchor}</a>{/HELP_URL?}</td>
256          <td align="right">{META_STRING}</td>
257        </tr>
258      </table>
259    </td>
260 EOF
261                 chomp $thing;
262                 return $thing;
263         },
264         image_meta => sub {
265                 my $opt = shift;
266                 my $span = shift;
267                 $opt->{break_template} ||= <<EOF;
268 $opt->{spacer_row}
269 <tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}>{ROW}</td></tr>
270 EOF
271                 my $thing = <<EOF;
272    <td$opt->{label_cell_extra}> 
273      {BLABEL}{LABEL}{ELABEL}
274    </td>
275    <td$opt->{data_cell_extra}\{COLSPAN}>
276      <table cellspacing="0" cellmargin="0" width="100%">
277        <tr> 
278          <td$opt->{widget_cell_extra}>
279            {WIDGET}
280          </td>
281          <td$opt->{help_cell_extra}>{TKEY}{HELP?}{HELP}{/HELP?}{HELP:}&nbsp;{/HELP:}{HELP_URL?}<br><a href="{HELP_URL}">$opt->{help_anchor}</a>{/HELP_URL?}</td>
282          <td align="right">{META_STRING}</td>
283        </tr>
284      </table>
285    </td>
286 EOF
287                 chomp $thing;
288                 return $thing;
289         },
290         simple_help_below => sub {
291                 my $opt = shift;
292                 my $thing = <<EOF;
293    <td$opt->{label_cell_extra}> 
294      {BLABEL}{LABEL}{ELABEL}
295    </td>
296    <td$opt->{data_cell_extra}\{COLSPAN}>
297                                 {WIDGET}
298                                 {HELP_EITHER?}<br$Trailer>{/HELP_EITHER?}
299                                 {HELP}{HELP_URL?}<br$Trailer><a href="{HELP_URL}">$opt->{help_anchor}</a>{/HELP_URL?}
300                                 {META_URL?}<a href="{META_URL}">$opt->{meta_anchor}</a>{/META_URL?}
301                         </td>
302                 </tr>
303         </table>
304    </td>
305 EOF
306                 chomp $thing;
307                 return $thing;
308         },
309         simple_icon_help => sub {
310                 my $opt = shift;
311                 $opt->{help_icon} ||= '/icons/small/unknown.gif';
312                 my $thing = <<EOF;
313    <td$opt->{label_cell_extra}> 
314      {BLABEL}{LABEL}{ELABEL}
315    </td>
316    <td$opt->{data_cell_extra}\{COLSPAN}>
317         <table width="100%">
318                 <tr>
319                         <td style="padding-left: 3px">
320                                 {WIDGET}
321                         </td>
322                         <td align="right">
323                                 {HELP_EITHER?}&nbsp;<a href="{HELP_URL?}{HELP_URL}{/HELP_URL?}{HELP_URL:}javascript:alert('{HELP}'); void(0){/HELP_URL:}" title="{HELP}"><img src="$opt->{help_icon}" border="0"></a>{/HELP_EITHER?}&nbsp;{META_URL?}<a href="{META_URL}">$opt->{meta_anchor}</a>{/META_URL?}
324                         </td>
325                 </tr>
326         </table>
327    </td>
328 EOF
329                 chomp $thing;
330                 return $thing;
331         },
332 );
333
334 my %dt_map = qw/
335  simple_row            1
336  text_help             2
337  simple_icon_help      3
338  over_under            4
339  simple_help_below     5
340  image_meta            6
341  three_column          7
342  nospace               8
343 /;
344
345 for(keys %dt_map) {
346         $Display_type{$dt_map{$_}} = $Display_type{$_}
347                 if $Display_type{$_};
348         $Display_options{$dt_map{$_}} = $Display_options{$_}
349                 if $Display_options{$_};
350 }
351
352 %Style_sheet = (
353         default => <<EOF,
354 <style type="text/css">
355 .rborder {
356         background-color: #CCCCCC;
357         margin: 0;;
358         padding: 2
359 }
360
361 .rhead {
362         background-color: #E6E6E6;;
363         color: #000000;
364         font-family: Arial, Helvetica, sans-serif;
365         font-size: 12px
366 }
367
368 A.rhead:active,A.rhead:hover {
369         color: #000000;
370         font-size: 12px;
371         text-decoration: underline;
372 }
373
374 A.rhead:link,A.rhead:visited {
375         color: #000000;
376         font-size: 12px;
377         text-decoration:none;
378 }
379
380 .rheadBold {
381         background-color: #E6E6E6;
382         color: #000000;
383         font-family: Arial, Helvetica, sans-serif;
384         font-size: 12px;
385         font-weight: bold;;
386         padding: 4px
387 }
388
389 .rheader {
390         background-color: #999999;
391         color: #663333;
392 }
393
394 .rmarq {
395         background-color: #999999;;
396         color: #FFFFFF;
397         font-size: 12px;
398         font-weight: bold
399 }
400
401 A.rmarq:active,A.rmarq:link,A.rmarq:visited {
402         color: #FFFFCC;
403         font-size: 12px;
404         font-weight: bold;
405         text-decoration:none;
406 }
407
408 A.rmarq:hover {
409         color: #FFFF99;
410         font-size: 12px;
411         font-weight: bold;
412         text-decoration: underline;
413 }
414
415 .rnobg {
416         color: #000000;
417         font-family: Arial, Helvetica, sans-serif;
418         font-size: 12px;
419         padding: 4px;
420 }
421
422 .rnorm {
423         background-color: #FFFFFF;
424         border: 1px solid #CCCCCC;
425 }
426
427 .rowalt {
428         background-color: #EAF1FB;
429         color: #000000;
430         font-family: Arial, Helvetica, sans-serif;
431         font-size: 12px;
432         padding: 4px;
433 }
434
435 A.rowalt:hover,A.rowalt:hover,A.rownorm:active,A.rownorm:hover {
436         color: #333333;
437         font-family: Arial, Helvetica, sans-serif;
438         font-size: 12px;
439         text-decoration: underline;
440 }
441
442 A.rowalt:link,A.rowalt:visited,A.rownorm:link,A.rownorm:visited {
443         color: #333333;
444         font-family: Arial, Helvetica, sans-serif;
445         font-size: 12px;
446         text-decoration:none;
447 }
448
449 .rownorm {
450         background-color: #FFFFFF;
451         color: #000000;
452         font-family: Arial, Helvetica, sans-serif;
453         font-size: 12px;
454         padding: 4px;
455 }
456
457 .rownormbold {
458         background-color: #FFFFFF;
459         color: #000000;
460         font-family: Arial, Helvetica, sans-serif;
461         font-size: 12px;
462         font-weight: bold;;
463         padding: 4px
464 }
465
466 .rseparator {
467         background-color: #CCCCCC;
468 }
469
470 .rshade {
471         background-color: #E6E6E6;
472         color: #000000;
473         font-family: Arial, Helvetica, sans-serif;
474         font-size: 12px;
475         padding: 4px;
476 }
477
478 .rspacer {
479         background-color: #999999;
480         margin: 0;;
481         padding: 0
482 }
483
484 .rsubbold {
485         background-color: #FFFFFF;
486         color: #808080;
487         font-family: Arial, Helvetica, sans-serif;
488         font-size: 12px;
489         font-weight: bold;;
490         padding: 4px
491 }
492
493 .rtitle {
494         background-color: #808080;;
495         color: #FFFFFF;
496         font-family: Verdana, Arial, Helvetica, sans-serif;
497         font-size: 12px;
498         font-weight: bold
499 }
500
501 A.rtitle:active,A.rtitle:link,A.rtitle:visited {
502         color: #FFFFCC;
503         font-size: 12px;
504         font-weight: bold;
505         text-decoration:none;
506 }
507
508
509 .s1,.s2 {
510         color: #666666;;
511         font-family: Verdana, Arial, Helvetica, sans-serif;
512         font-size: 10px
513 }
514
515 .s3 {
516         color: #333333;;
517         font-family: Verdana, Arial, Helvetica, sans-serif;
518         font-size: 10px
519 }
520
521 .s4 {
522         color: #666666;
523         font-family: Verdana, Arial, Helvetica, sans-serif;
524         font-size: 10px;
525         width: 100%;
526 }
527
528
529 .rbreak {
530         background-color: #FFFFFF;
531 }
532
533
534 .cborder {
535         background-color: #999999;
536         padding: 0;
537 }
538
539 .cbreak {
540         background-color: #EEEEEE;
541         border-left: 1px solid #999999;
542         font-size: 11px;;
543         font-weight: bold
544 }
545
546 .cdata {
547         border-bottom: 1px solid #CCCCCC;
548         border-right: 1px solid #CCCCCC;
549         border-top: 1px solid #CCCCCC;
550         font-size: 11px;;
551         margin-right: 4px;
552         padding-right: 2px;
553         vertical-align: top
554 }
555
556 .cerror {
557         color: red;
558         font-size: 11px;
559 }
560
561 .cheader {
562         color: #663333;
563         font-size: 11px;;
564         font-weight: bold
565 }
566
567 .chelp,.rhint {
568         color: #AFABA5;
569         font-family: Arial, Helvetica, sans-serif;
570         font-size: 12px;
571         padding: 4px;
572 }
573
574 .clabel {
575         border-bottom: 1px solid #CCCCCC;
576         border-left: 1px solid #CCCCCC;
577         border-top: 1px solid #CCCCCC;
578         font-size: 11px;
579         vertical-align: top;
580         font-weight: medium;
581         padding-left: 5px;;
582         text-align: left
583 }
584
585 .cmiddle {
586         border-bottom: 1px solid #CCCCCC;
587         border-top: 1px solid #CCCCCC;
588         font-size: 11px;
589         font-weight: medium;
590         padding-left: 5px;;
591         text-align: middle
592 }
593
594 .cmessage {
595         color: green;
596         font-size: 11px;
597 }
598
599 A:link.ctitle,A:visited.ctitle {
600         color: white;
601         font-size: 11px;;
602         font-weight: bold;
603         text-decoration: none
604 }
605
606 A:hover.ctitle,A:active.ctitle {
607         color: yellow;
608         font-size: 11px;;
609         font-weight: bold;
610         text-decoration: underline
611 }
612
613 .ctitle {
614         font-size: 11px;;
615         font-weight: bold
616 }
617
618 .cwidget {
619         font-size: 11px;;
620         vertical-align: center
621 }
622
623 </style>
624 EOF
625 );
626
627 sub expand_values {
628         my $val = shift;
629         return $val unless $val =~ /\[/;
630         $val =~ s/\[cgi\s+([^\[]+)\]/$CGI::values{$1}/ig;
631         $val =~ s/\[var\s+([^\[]+)\]/$::Variable->{$1}/ig;
632         $val =~ s/\[value\s+([^\[]+)\]/$::Values->{$1}/ig;
633         return $val;
634 }
635
636 sub widget_meta {
637         my ($type,$opt) = @_;
638         my $meta = meta_record("_widget::$type", $opt->{view}, $opt->{meta_table}, 1);
639         return $meta if $meta;
640         my $w = $Vend::Cfg->{CodeDef}{Widget};
641         if($w and $w->{Widget}{$type}) {
642                 my $string;
643                 return undef unless $string = $w->{ExtraMeta}{$type};
644                 return get_option_hash($string);
645         }
646
647         $w = $Global::CodeDef->{Widget};
648         if($w and $w->{Widget}{$type}) {
649                 my $string;
650                 return undef unless $string = $w->{ExtraMeta}{$type};
651                 return get_option_hash($string);
652         }
653
654         return $Vend::Form::ExtraMeta{$type};
655 }
656
657 sub meta_record {
658         my ($item, $view, $mdb, $extended_only, $overlay) = @_;
659
660 #::logDebug("meta_record: item=$item view=$view mdb=$mdb");
661         return undef unless $item;
662
663         my $mtable;
664         if(! ref ($mdb)) {
665                 $mtable = $mdb || $::Variable->{UI_META_TABLE} || 'mv_metadata';
666 #::logDebug("meta_record mtable=$mtable");
667                 $mdb = database_exists_ref($mtable)
668                         or return undef;
669         }
670 #::logDebug("meta_record has an item=$item and mdb=$mdb");
671
672         my $record;
673
674         my $mkey = $view ? "${view}::$item" : $item;
675
676         if( ref ($mdb) eq 'HASH') {
677                 $record = $mdb;
678         }
679         else {
680                 $record = $mdb->row_hash($mkey);
681 #::logDebug("used mkey=$mkey to select record=$record");
682         }
683
684         $record ||= $mdb->row_hash($item) if $view and $mdb;
685 #::logDebug("meta_record  record=$record");
686
687         return undef if ! $record;
688
689         # Get additional settings from extended field, which is a serialized
690         # hash
691         my $hash;
692         if(! $record->{extended}) {
693                         return undef if $extended_only;
694         }
695         else {
696                 ## From Vend::Util
697                 $hash = get_option_hash($record->{extended});
698                 $record = {} if $extended_only;
699                 if(ref $hash eq 'HASH') {
700                         @$record{keys %$hash} = values %$hash;
701                 }
702                 else {
703                         undef $hash;
704                         return undef if $extended_only;
705                 }
706         }
707
708         # Allow view settings to be placed in the extended area
709         if($view and $hash and $hash->{view}) {
710                 my $view_hash = $record->{view}{$view};
711                 ref $view_hash
712                         and @$record{keys %$view_hash} = values %$view_hash;
713         }
714
715         # Allow overlay of certain settings
716         if($overlay and $record->{overlay}) {
717                 my $ol_hash = $record->{overlay}{$overlay};
718                 Vend::Util::copyref($ol_hash, $record) if $ol_hash;
719         }
720 #::logDebug("return meta_record=" . ::uneval($record) );
721         return $record;
722 }
723
724 my $base_entry_value;
725
726 sub display {
727         my ($table,$column,$key,$opt) = @_;
728
729         if( ref($opt) ne 'HASH' ) {
730                 $opt = get_option_hash($opt);
731         }
732
733         my $template = $opt->{type} eq 'hidden' ? '' : $opt->{template};
734
735         if($opt->{override}) {
736                 $opt->{value} = defined $opt->{default} ? $opt->{default} : '';
737         }
738
739         if(! defined $opt->{value} and $table and $column and length($key)) {
740                 $opt->{value} = tag_data($table, $column, $key);
741                 $opt->{already_got_data} = 1;
742         }
743
744         my $mtab;
745         my $record;
746
747         my $no_meta = $opt->{ui_no_meta_display};
748
749         METALOOK: {
750                 ## No meta display wanted
751                 last METALOOK if $no_meta;
752                 ## No meta display possible
753                 $table and $column or $opt->{meta}
754                         or last METALOOK;
755
756                 ## We get a metarecord directly, though why it would be here
757                 ## and not in options I don't know
758                 if($opt->{meta} and ref($opt->{meta}) eq 'HASH') {
759                         $record = $opt->{meta};
760                         last METALOOK;
761                 }
762
763                 $mtab = $opt->{meta_table} || $::Variable->{UI_META_TABLE} || 'mv_metadata'
764                         or last METALOOK;
765                 my $meta = Vend::Data::database_exists_ref($mtab)
766                         or do {
767                                 ::logError("non-existent meta table: %s", $mtab);
768                                 undef $mtab;
769                                 last METALOOK;
770                         };
771
772                 my $view = $opt->{view} || $opt->{arbitrary};
773
774                 ## This is intended to trigger on the first access
775                 if($table eq $mtab and $column eq $meta->config('KEY')) {
776                         if($view and $opt->{value} !~ /::.+::/) {
777                                 $base_entry_value = ($opt->{value} =~ /^([^:]+)::(\w+)$/)
778                                                                         ? $1
779                                                                         : $opt->{value};
780                         }
781                         else {
782                                 $base_entry_value = $opt->{value} =~ /(\w+)::/
783                                                                         ? $1
784                                                                         : $opt->{value};
785                         }
786                 }
787
788                 my (@tries) = "${table}::$column";
789                 unshift @tries, "${table}::${column}::$key"
790                         if length($key) and $opt->{specific};
791
792                 my $sess = $Vend::Session->{mv_metadata} || {};
793
794                 push @tries, { type => $opt->{type} }
795                         if $opt->{type} || $opt->{label};
796
797                 for my $metakey (@tries) {
798                         ## In case we were passed a meta record
799                         last if $record = $sess->{$metakey} and ref $record;
800                         $record = meta_record($metakey, $view, $meta)
801                                 and last;
802                 }
803         }
804
805         my $w;
806
807         METAMAKE: {
808                 last METAMAKE if $no_meta;
809                 if( ! $record ) {
810                         $record = { %$opt };
811                 }
812                 else {
813                         ## Here we allow override with the display tag, even with views and
814                         ## extended
815                         my @override = qw/
816                                                                 append
817                                                                 attribute
818                                                                 db
819                                                                 callback_prescript
820                                                                 callback_postscript
821                                                                 class
822                                                                 default
823                                                                 extra
824                                                                 disabled
825                                                                 field
826                                                                 form
827                                                                 form_name
828                                                                 filter
829                                                                 height
830                                                                 help
831                                                                 help_url
832                                                                 id
833                                                                 label
834                                                                 js_check
835                                                                 lookup
836                                                                 lookup_exclude
837                                                                 lookup_query
838                                                                 maxlength
839                                                                 name
840                                                                 options
841                                                                 outboard
842                                                                 passed
843                                                                 pre_filter
844                                                                 prepend
845                                                                 table
846                                                                 type
847                                                                 type_empty
848                                                                 width
849                                                                 /;
850                         for(@override) {
851                                 delete $record->{$_} if ! length($record->{$_});
852                                 next unless defined $opt->{$_};
853                                 $record->{$_} = $opt->{$_};
854                         }
855                 }
856
857                 if($record->{type_empty} and length($opt->{value}) == 0) {
858                         $record->{type} = $record->{type_empty};
859                 }
860                 else {
861                         $record->{type} ||= $opt->{default_widget};
862                 }
863
864                 $record->{name} ||= $column;
865 #::logDebug("record now=" . ::uneval($record));
866
867                 if($record->{options} and $record->{options} =~ /^[\w:,]+$/) {
868 #::logDebug("checking options");
869                         PASS: {
870                                 my $passed = $record->{options};
871
872                                 if($passed eq 'tables') {
873                                         my @tables = $Tag->list_databases();
874                                         $record->{passed} = join (',', "=--none--", @tables);
875                                 }
876                                 elsif($passed =~ /^(?:filters|\s*codedef:+(\w+)(:+(\w+))?\s*)$/i) {
877                                         my $tag = $1 || 'filters';
878                                         my $mod = $3;
879                                         $record->{passed} = Vend::Util::codedef_options($tag, $mod);
880                                 }
881                                 elsif($passed =~ /^columns(::(\w*))?\s*$/) {
882                                         my $total = $1;
883                                         my $tname = $2 || $record->{db} || $table;
884                                         if ($total eq '::' and $base_entry_value) {
885                                                 $tname = $base_entry_value;
886                                         }
887                                         $record->{passed} = join ",",
888                                                                                         "=--none--",
889                                                                                         $Tag->db_columns($tname),
890                                                                                 ;
891                                 }
892                                 elsif($passed =~ /^keys(::(\w+))?\s*$/) {
893                                         my $tname = $2 || $record->{db} || $table;
894                                         $record->{passed} = join ",",
895                                                                                         "=--none--",
896                                                                                         $Tag->list_keys($tname),
897                                                                                 ;
898                                 }
899                         }
900                 }
901
902 #::logDebug("checking for custom widget");
903                 if ($record->{type} =~ s/^custom\s+//s) {
904                         my $wid = lc $record->{type};
905                         $wid =~ tr/-/_/;
906                         $record->{attribute} ||= $column;
907                         $record->{table}     ||= $mtab;
908                         $record->{rows}      ||= $record->{height};
909                         $record->{cols}      ||= $record->{width};
910                         $record->{field}     ||= 'options';
911                         $record->{name}      ||= $column;
912                         eval {
913                                 $w = $Tag->$wid($record->{name}, $opt->{value}, $record, $opt);
914                         };
915                         if($@) {
916                                 ::logError("error using custom widget %s: %s", $wid, $@);
917                         }
918                         last METAMAKE;
919                 }
920
921                 $opt->{restrict_allow} ||= $record->{restrict_allow};
922 #::logDebug("formatting prepend/append/lookup_query name=$opt->{name} restrict_allow=$opt->{restrict_allow}");
923                 for(qw/append prepend lookup_query/) {
924                         next unless $record->{$_};
925                         if($opt->{restrict_allow}) {
926                                 $record->{$_} = $Tag->restrict({
927                                                                         log => 'none',
928                                                                         enable => $opt->{restrict_allow},
929                                                                         disable => $opt->{restrict_deny},
930                                                                         body => $record->{$_},
931                                                                 });
932                         }
933                         else {
934                                 $record->{$_} = expand_values($record->{$_});
935                         }
936                         $record->{$_} = Vend::Util::resolve_links($record->{$_});
937                         $record->{$_} =~ s/_UI_VALUE_/$opt->{value}/g;
938                         $record->{$_} =~ /_UI_URL_VALUE_/
939                                 and do {
940                                         my $tmp = $opt->{value};
941                                         $tmp =~ s/(\W)/sprintf '%%%02x', ord($1)/eg;
942                                         $record->{$_} =~ s/_UI_URL_VALUE_/$tmp/g;
943                                 };
944                         $record->{$_} =~ s/_UI_TABLE_/$table/g;
945                         $record->{$_} =~ s/_UI_COLUMN_/$column/g;
946                         $record->{$_} =~ s/_UI_KEY_/$key/g;
947                 }
948
949                 if($opt->{opts}) {
950                         my $r = get_option_hash(delete $opt->{opts});
951                         for my $k (keys %$r) {
952                                 $record->{$k} = $r->{$k};
953                         }
954                 }
955
956
957 #::logDebug("overriding defaults");
958 #::logDebug("passed=$record->{passed}") if $record->{debug};
959                 my %things = (
960                         attribute       => $column,
961                         cols            => $opt->{cols}   || $record->{width},
962                         passed          => $record->{options},
963                         rows            => $opt->{rows} || $record->{height},
964                         value           => $opt->{value},
965                         applylocale => $opt->{applylocale},
966                 );
967
968                 while( my ($k, $v) = each %things) {
969                         next if length $record->{$k};
970                         next unless defined $v;
971                         $record->{$k} = $v;
972                 }
973
974 #::logDebug("calling Vend::Form with record=" . ::uneval($record));
975                 if($record->{save_defaults}) {
976                         my $sd = $Vend::Session->{meta_defaults} ||= {};
977                         $sd = $sd->{"${table}::$column"} ||= {}; 
978                         while (my ($k,$v) = each %$record) {
979                                 next if ref($v) eq 'CODE';
980                                 $sd->{$k} = $v;
981                         }
982                 }
983
984                 $w = Vend::Form::display($record);
985                 if($record->{filter}) {
986                         $w .= qq{<input type="hidden" name="ui_filter:$record->{name}" value="};
987                         $w .= $record->{filter};
988                         $w .= '">';
989                 }
990         }
991
992         if(! defined $w) {
993                 my $text = $opt->{value};
994                 my $iname = $opt->{name} || $column;
995
996                 # Count lines for textarea
997                 my $count;
998                 $count = $text =~ s/(\r\n|\r|\n)/$1/g;
999
1000                 HTML::Entities::encode($text, $ESCAPE_CHARS::std);
1001                 my $size;
1002                 if ($count) {
1003                         $count++;
1004                         $count = 20 if $count > 20;
1005                         $w = <<EOF;
1006         <textarea name="$iname" cols="60" rows="$count">$text</textarea>
1007 EOF
1008                 }
1009                 elsif ($text =~ /^\d+$/) {
1010                         my $cur = length($text);
1011                         $size = $cur > 8 ? $cur + 1 : 8;
1012                 }
1013                 else {
1014                         $size = 60;
1015                 }
1016                         $w = <<EOF;
1017         <input name="$iname" size="$size" value="$text">
1018 EOF
1019         }
1020
1021         my $array_return = wantarray;
1022
1023 #::logDebug("widget=$w");
1024
1025         # don't output label if widget is hidden form variable only
1026         # and not an array type
1027         undef $template if $w =~ /^\s*<input\s[^>]*type\s*=\W*hidden\b[^>]*>\s*$/i;
1028
1029         return $w unless $template || $opt->{return_hash} || $array_return;
1030
1031         if($template and $template !~ /\s/) {
1032                 $template = <<EOF;
1033 <tr>
1034 <td>
1035         <b>\$LABEL\$</b>
1036 </td>
1037 <td valign="top">
1038         <table cellspacing="0" cellmargin="0"><tr><td>\$WIDGET\$</td><td>\$HELP\${HELP_URL}<br$Vend::Xtrailer><a href="\$HELP_URL\$">help</a>{/HELP_URL}</td></tr></table>
1039 </td>
1040 </tr>
1041 EOF
1042         }
1043
1044         $record->{label} ||= $column;
1045
1046         my %sub = (
1047                 WIDGET          => $w,
1048                 HELP            => $opt->{applylocale}
1049                                                 ? errmsg($record->{help})
1050                                                 : $record->{help},
1051         META_URL    => $opt->{meta_url},
1052                 HELP_URL        => $record->{help_url},
1053                 LABEL           => $opt->{applylocale}
1054                                                 ? errmsg($record->{label})
1055                                                 : $record->{label},
1056         );
1057 #::logDebug("passed meta_url=$opt->{meta_url}");
1058       $sub{HELP_EITHER} = $sub{HELP} || $sub{HELP_URL};
1059
1060         if($opt->{return_hash}) {
1061                 $sub{OPT} = $opt;
1062                 $sub{RECORD} = $record;
1063                 return \%sub;
1064         }
1065         elsif($array_return) {
1066                 return ($w, $sub{LABEL}, $sub{HELP}, $record->{help_url});
1067         }
1068         else {
1069                 # Strip the {TAG} {/TAG} pairs if nothing there
1070                 $template =~ s#{([A-Z_]+)}(.*?){/\1}#$sub{$1} ? $2: '' #ges;
1071                 # Insert the TAG
1072               $sub{HELP_URL} ||= 'javascript:void(0)';
1073                 $template =~ s/\$([A-Z_]+)\$/$sub{$1}/g;
1074 #::logDebug("substituted template is: $template");
1075                 return $template;
1076         }
1077 }
1078
1079 sub tabbed_display {
1080         my ($tit, $cont, $opt) = @_;
1081         
1082         $opt ||= {};
1083
1084         my @colors;
1085         $opt->{tab_bgcolor_template} ||= '#xxxxxx';
1086         $opt->{tab_height} ||= '20'; $opt->{tab_width} ||= '120';
1087         $opt->{panel_id} ||= 'mvpan';
1088         $opt->{tab_horiz_offset} ||= '10';
1089         $opt->{tab_vert_offset} ||= '8';
1090         my $width_height;
1091         $opt->{tab_style} ||= q{
1092                                                                 text-align:center;
1093                                                                 font-family: sans-serif;
1094                                                                 line-height:150%;
1095                                                                 font-size: smaller;
1096                                                                 border:2px;
1097                                                                 border-color:#999999;
1098                                                                 border-style:outset;
1099                                                                 border-bottom-style:none;
1100                                                         };
1101         if($opt->{ui_style}) {
1102                 $opt->{panel_style} ||= q{ 
1103                                                                         padding: 0;
1104                                                                 };
1105                 $width_height = '';
1106         }
1107         else {
1108                 $opt->{panel_style} ||= q{ 
1109                                                                         font-family: sans-serif;
1110                                                                         font-size: smaller;
1111                                                                         padding: 0;
1112                                                                         border: 2px;
1113                                                                         border-color:#999999;
1114                                                                         border-style:outset;
1115                                                                 };
1116                 $opt->{panel_height} ||= '600';
1117                 $opt->{panel_width} ||= '800';
1118                 $width_height = <<EOF;
1119    width: $opt->{panel_width}px;
1120    height: $opt->{panel_height}px;
1121 EOF
1122         }
1123
1124         $opt->{panel_shade} ||= 'e';
1125
1126         my @chars = reverse(0 .. 9, 'a' .. $opt->{panel_shade});
1127         my $id = $opt->{panel_id};
1128         my $vpf = $id . '_';
1129         my $num_panels = scalar(@$cont);
1130         my $tabs_per_row = int( $opt->{panel_width} / $opt->{tab_width}) || 1;
1131     my $num_rows = POSIX::ceil( $num_panels / $tabs_per_row);
1132         my $width = $opt->{panel_width};
1133         my $height = $opt->{tab_height} * $num_rows + $opt->{panel_height};
1134         my $panel_y;
1135         my $int1;
1136         my $int2;
1137         if($opt->{ui_style}) {
1138                 $panel_y = 2;
1139                 $int1 = $int2 = 0;
1140         }
1141         else {
1142           $panel_y =
1143                 $num_rows
1144                 * ($opt->{tab_height} - $opt->{tab_vert_offset})
1145                 + $opt->{tab_vert_offset};
1146                 $int1 = $panel_y - 2;
1147                 $int2 = $opt->{tab_height} * $num_rows;
1148         }
1149         for(my $i = 0; $i < $num_panels; $i++) {
1150                 my $c = $opt->{tab_bgcolor_template} || '#xxxxxx';
1151                 $c =~ s/x/$chars[$i] || $opt->{panel_shade}/eg;
1152                 $colors[$i] = $c;
1153         }
1154         my $cArray = qq{var ${vpf}colors = ['} . join("','", @colors) . qq{'];};
1155 #::logDebug("num rows=$num_rows");
1156         my $out = <<EOF;
1157 <script language="JavaScript">
1158 <!--
1159 var ${vpf}panelID = "$id"
1160 var ${vpf}numDiv = $num_panels;
1161 var ${vpf}numRows = $num_rows;
1162 var ${vpf}tabsPerRow = $tabs_per_row;
1163 var ${vpf}numLocations = ${vpf}numRows * ${vpf}tabsPerRow
1164 var ${vpf}tabWidth = $opt->{tab_width}
1165 var ${vpf}tabHeight = $opt->{tab_height}
1166 var ${vpf}vOffset = $opt->{tab_vert_offset};
1167 var ${vpf}hOffset = $opt->{tab_horiz_offset};
1168 $cArray
1169
1170 var ${vpf}uptabs = new Array;
1171 var ${vpf}dntabs = new Array;
1172 var ${vpf}divLocation = new Array(${vpf}numLocations)
1173 var ${vpf}newLocation = new Array(${vpf}numLocations)
1174 for(var i=0; i<${vpf}numLocations; ++i) {
1175         ${vpf}divLocation[i] = i
1176         ${vpf}newLocation[i] = i
1177 }
1178
1179 function ${vpf}getDiv(s,i) {
1180         var div
1181         if (document.layers) {
1182                 div = document.layers[${vpf}panelID].layers[panelID+s+i]
1183         } else if (document.all && !document.getElementById) {
1184                 div = document.all[${vpf}panelID+s+i]
1185         } else {
1186                 div = document.getElementById(${vpf}panelID+s+i)
1187         }
1188         return div
1189 }
1190
1191 function ${vpf}setZIndex(div, zIndex) {
1192         if (document.layers) div.style = div;
1193         div.style.zIndex = zIndex
1194 }
1195
1196 function ${vpf}updatePosition(div, newPos) {
1197         ${vpf}newClip=${vpf}tabHeight*(Math.floor(newPos/${vpf}tabsPerRow)+1)
1198         if (document.layers) {
1199                 div.style=div;
1200                 div.clip.bottom=${vpf}newClip; // clip off bottom
1201         } else {
1202                 div.style.clip="rect(0 auto "+${vpf}newClip+" 0)"
1203         }
1204         div.style.top = (${vpf}numRows-(Math.floor(newPos/${vpf}tabsPerRow) + 1)) * (${vpf}tabHeight-${vpf}vOffset)
1205         div.style.left = (newPos % ${vpf}tabsPerRow) * ${vpf}tabWidth + (${vpf}hOffset * (Math.floor(newPos / ${vpf}tabsPerRow)))
1206 }
1207
1208 function ${vpf}tripTab(n) {
1209         // n is the ID of the division that was clicked
1210         // firstTab is the location of the first tab in the selected row
1211         var el;
1212         for(var i = 0; i < ${vpf}dntabs.length; i++) {
1213                 el = document.getElementById('${vpf}td' + i);
1214                 if(el != undefined) {
1215                         el.innerHTML = ${vpf}dntabs[ i ];
1216                         el.style.backgroundColor = '#B4B0AA';
1217                 }
1218         }
1219         el = document.getElementById('${vpf}td' + n);
1220         el.innerHTML = ${vpf}uptabs[ n ];
1221         el.style.backgroundColor = '#D4D0C8';
1222         // Set tab positions & zIndex
1223         // Update location
1224         var j = 1;
1225         for(var i=0; i<${vpf}numDiv; ++i) {
1226                 var loc = ${vpf}newLocation[i]
1227                 var div = ${vpf}getDiv("panel",i)
1228                 if(i == n) {
1229                         ${vpf}setZIndex(div, ${vpf}numLocations +1);
1230                         div.style.display = 'block';
1231                         div.style.backgroundColor = ${vpf}colors[0];
1232                 }
1233                 else {
1234                         ${vpf}setZIndex(div, ${vpf}numLocations - loc)
1235                         div.style.display = 'none';
1236                         div.style.backgroundColor = ${vpf}colors[j++];
1237                 }
1238                 ${vpf}divLocation[i] = loc
1239         }
1240 }
1241
1242 function ${vpf}selectTab(n) {
1243         // n is the ID of the division that was clicked
1244         // firstTab is the location of the first tab in the selected row
1245         var firstTab = Math.floor(${vpf}divLocation[n] / ${vpf}tabsPerRow) * ${vpf}tabsPerRow
1246         // newLoc is its new location
1247         for(var i=0; i<${vpf}numDiv; ++i) {
1248                 // loc is the current location of the tab
1249                 var loc = ${vpf}divLocation[i]
1250                 // If in the selected row
1251                 if(loc >= firstTab && loc < (firstTab + ${vpf}tabsPerRow)) ${vpf}newLocation[i] = (loc - firstTab)
1252                 else if(loc < ${vpf}tabsPerRow) ${vpf}newLocation[i] = firstTab+(loc % ${vpf}tabsPerRow)
1253                 else ${vpf}newLocation[i] = loc
1254         }
1255         // Set tab positions & zIndex
1256         // Update location
1257         var j = 1;
1258         for(var i=0; i<${vpf}numDiv; ++i) {
1259                 var loc = ${vpf}newLocation[i]
1260                 var div = ${vpf}getDiv("panel",i)
1261                 var tdiv = ${vpf}getDiv("tab",i)
1262                 if(i == n) {
1263                         ${vpf}setZIndex(div, ${vpf}numLocations +1);
1264                         div.style.display = 'block';
1265                         tdiv.style.backgroundColor = ${vpf}colors[0];
1266                         div.style.backgroundColor = ${vpf}colors[0];
1267                 }
1268                 else {
1269                         ${vpf}setZIndex(div, ${vpf}numLocations - loc)
1270                         div.style.display = 'none';
1271                         tdiv.style.backgroundColor = ${vpf}colors[j];
1272                         div.style.backgroundColor = ${vpf}colors[j++];
1273                 }
1274                 ${vpf}divLocation[i] = loc
1275                 ${vpf}updatePosition(tdiv, loc)
1276                 if(i == n) ${vpf}setZIndex(tdiv, ${vpf}numLocations +1)
1277                 else ${vpf}setZIndex(tdiv,${vpf}numLocations - loc)
1278         }
1279 }
1280
1281 //-->
1282 </script>
1283 <style type="text/css">
1284 <!--
1285 .${id}tab {
1286         font-weight: bold;
1287         width:$opt->{tab_width}px;
1288         margin:0px;
1289         height: ${int2}px;
1290         position:relative;
1291         $opt->{tab_style}
1292         }
1293
1294 .${id}panel {
1295         position:relative;
1296         width: $opt->{panel_width}px;
1297         height: $opt->{panel_height}px;
1298         left:0px;
1299         top:${int1}px;
1300         margin:0px;
1301         $opt->{panel_style}
1302         }
1303 -->
1304 </style>
1305 EOF
1306         my $s1 = '';
1307         my $s2 = '';
1308         my $ibase = $Tag->image({
1309                                                         ui                      => $Vend::admin,
1310                                                         dir_only        => 1,
1311                                                         secure          => $Vend::admin && $::Variable->{UI_SECURE},
1312                                                 });
1313         $opt->{clear_image} ||= 'bg.gif';
1314         my $clear = "$ibase/$opt->{clear_image}";
1315         my @dntabs;
1316         my @uptabs;
1317         for(my $i = 0; $i < $num_panels; $i++) {
1318                 my $zi = $num_panels - $i;
1319                 my $pnum = $i + 1;
1320                 my $left = (($i % $tabs_per_row)
1321                                         * $opt->{tab_width}
1322                                         + ($opt->{tab_horiz_offset}
1323                                         * (int($i / $tabs_per_row))));
1324                 my $top = ( $num_rows - (int($i / $tabs_per_row) + 1))
1325                                         - ($opt->{tab_height} - $opt->{tab_vert_offset});
1326                 my $cliprect = $opt->{tab_height} * (int($i / $tabs_per_row) + 1);
1327                 $s1 .= <<EOF;
1328 <div id="${id}panel$i"
1329                 class="${id}panel"
1330                 style="
1331                         background-color: $colors[$i]; 
1332                         z-index:$zi
1333                 ">
1334 $opt->{panel_prepend}
1335 $cont->[$i]
1336 $opt->{panel_append}
1337 </div>
1338 EOF
1339                 if($opt->{ui_style}) {
1340                         $s2 .= <<EOF;
1341 <td class="subtabdown" id="${vpf}td$i"> 
1342 EOF
1343
1344                         $dntabs[$i] = <<EOF;
1345         <table width="100%" border="0" cellspacing="0" cellpadding="0">
1346           <tr> 
1347                  <td class="subtabdownleft"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td>
1348                  <td nowrap class="subtabdownfill"><a href="javascript:${vpf}tripTab($i,1)" class="subtablink">$tit->[$i]</a></td>
1349                  <td class="subtabdownright"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td>
1350           </tr>
1351           <tr> 
1352                  <td colspan="3" class="darkshade"><img src="$clear" height="1"></td>
1353           </tr>
1354           <tr> 
1355                  <td colspan="3" class="lightshade"><img src="$clear" height="1"></td>
1356           </tr>
1357    </table>
1358 EOF
1359
1360                         $s2 .= $dntabs[$i];
1361
1362                         $uptabs[$i] = <<EOF;
1363         <table width="100%" border="0" cellspacing="0" cellpadding="0">
1364           <tr> 
1365                  <td class="subtableft"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td>
1366                  <td nowrap class="subtabfill"><a href="javascript:${vpf}tripTab($i,1)" class="subtablink">$tit->[$i]</a></td>
1367                  <td class="subtabright"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td>
1368           </tr>
1369           <tr> 
1370                  <td colspan="3" class="subtabfilllwr"><img src="$clear" height="1"></td>
1371           </tr>
1372         </table>
1373 EOF
1374                         $s2 .= "</td>\n";
1375
1376                 }
1377                 else {
1378                         $s1 .= <<EOF;
1379 <div
1380         onclick="${vpf}selectTab($i)"
1381         id="${id}tab$i"
1382         class="${id}tab"
1383         style="
1384                 position: absolute;
1385                 background-color: $colors[$i]; 
1386                 cursor: pointer;
1387                 left: ${left}px;
1388                 top: ${top}px;
1389                 z-index:$zi;
1390                 clip:rect(0 auto $cliprect 0);
1391                 ">
1392 $tit->[$i]
1393 </div>
1394 EOF
1395                 }
1396         }
1397
1398         my $start_index = $opt->{start_at_index} || 0;
1399         $start_index += 0;
1400         if($s2) {
1401                 $Tag->output_to('third_tabs', { name => 'third_tabs' }, $s2);
1402         }
1403         $out .= <<EOF;
1404 <div style="
1405                 position: relative;
1406                 left: 0; top: 0; width: 100%; height: 100%;
1407                 z-index: 0;
1408         ">
1409 $s1
1410 EOF
1411         if($s2) {
1412                 $out .= <<EOF;
1413 <script>
1414 EOF
1415                 for(my $i = 0; $i < @dntabs; $i++) {
1416                         $out .= "${vpf}uptabs[ $i ] = ";
1417                         $out .= $Tag->jsq($uptabs[$i]);
1418                         $out .= ";\n";
1419                         $out .= "${vpf}dntabs[ $i ] = ";
1420                         $out .= $Tag->jsq($dntabs[$i]);
1421                         $out .= ";\n";
1422                 }
1423                 $out .= <<EOF;
1424         ${vpf}tripTab($start_index);
1425 </script>
1426 EOF
1427         }
1428         else {
1429                 $out .= <<EOF;
1430 <script>
1431         ${vpf}selectTab($start_index);
1432 </script>
1433 EOF
1434         }
1435
1436         $out .= <<EOF;
1437 </div>
1438 EOF
1439
1440 }
1441
1442 my $tcount_all;
1443 my %alias;
1444 my %exclude;
1445 my %outhash;
1446 my @titles;
1447 my @controls;
1448 my $ctl_index = 0;
1449 my @out;
1450
1451 sub ttag {
1452         return 'TABLE_STD' . ++$tcount_all;
1453 }
1454
1455 sub add_exclude {
1456         my ($tag, $string) = @_;
1457 #::logDebug("calling add_exclude tag='$tag' string='$string'");
1458         return unless $string =~ /\S/;
1459         $exclude{$tag} ||= ' ';
1460         $exclude{$tag} .= "$string ";
1461 }
1462
1463 sub col_chunk {
1464         my $value = pop @_;
1465         my $tag = shift @_;
1466         my $exclude = shift @_;
1467         my @others = @_;
1468
1469         $tag = "COLUMN_$tag";
1470
1471 #::logDebug("$tag content length=" . length($value));
1472
1473         if(exists $outhash{$tag}) {
1474                 my $col = $tag;
1475                 $col =~ s/^COLUMN_//;
1476                 my $msg = errmsg("Column '%s' defined twice, skipping second.", $col);
1477                 Vend::Tags->warnings($msg);
1478                 return;
1479         }
1480
1481         $outhash{$tag} = $value;
1482
1483         if(@others) {
1484                 $alias{$tag} ||= [];
1485                 push @{$alias{$tag}}, @others;
1486         }
1487
1488         my $ctl = $controls[$ctl_index] ||= [];
1489         add_exclude($tag, $exclude) if $exclude;
1490
1491         return unless length($value);
1492
1493         push @$ctl, $tag;
1494         return;
1495 }
1496
1497 sub chunk_alias {
1498         my $tag = shift;
1499         $alias{$tag} ||= [];
1500         push @{$alias{$tag}}, @_;
1501         return;
1502 }
1503
1504 sub chunk {
1505         my $value = pop @_;
1506         my $tag = shift @_;
1507         my $exclude = shift @_;
1508         my @others = @_;
1509
1510         die "duplicate tag settor $tag" if exists $outhash{$tag};
1511         $outhash{$tag} = $value;
1512
1513 #::logDebug("$tag exclude=$exclude, content length=" . length($value));
1514
1515         if(@others) {
1516                 $alias{$tag} ||= [];
1517                 push @{$alias{$tag}}, @others;
1518         }
1519
1520         add_exclude($tag, $exclude) if $exclude =~ /\S/;
1521
1522         return unless length($value);
1523         push @out, $tag;
1524 }
1525
1526 sub resolve_exclude {
1527         my $exc = shift;
1528         while(my ($k, $v) = each %exclude) {
1529                 my %seen;
1530                 my @things = grep /\S/ && ! $seen{$_}++, split /\s+/, $v;
1531 #::logDebug("examining $k for $v");
1532                 for my $thing (@things) {
1533                         if($thing =~ s/^[^A-Z]//) {
1534 #::logDebug("examining $v for $thing!=$exc->{$thing}");
1535                                 $outhash{$k} = '' unless $exc->{$thing};
1536                         }
1537                         else {
1538 #::logDebug("examining $v for $thing=$exc->{$thing}");
1539                                 $outhash{$k} = '' if $exc->{$thing};
1540                         }
1541                 }
1542         }
1543 }
1544
1545 sub editor_init {
1546         undef $base_entry_value;
1547
1548         ## Why?
1549         Vend::Interpolate::init_calc() if ! $Vend::Calc_initialized;
1550         @out = ();
1551         @controls = ();
1552         @titles = ();
1553         %outhash = ();
1554         %exclude = ();
1555         %alias = ();
1556         $tcount_all = 0;
1557         $ctl_index = 0;
1558 }
1559
1560 my %o_default_length = (
1561         border_cell_class       => 'cborder',
1562         widget_cell_class       => 'cwidget',
1563         label_cell_class        => 'clabel',
1564         data_cell_class => 'cdata',
1565         help_cell_class => 'chelp',
1566         break_cell_class_first  => 'cbreakfirst',
1567         break_cell_class        => 'cbreak',
1568         spacer_row_class => 'rspacer',
1569         break_row_class => 'rbreak',
1570         title_row_class => 'rmarq',
1571         data_row_class => 'rnorm',
1572         ok_button_style => 'font-weight: bold; width: 40px; text-align: center',
1573 );
1574
1575 my %o_default_var = (qw/
1576         color_fail                      UI_CONTRAST
1577         color_success           UI_C_SUCCESS
1578 /);
1579
1580 my %o_default_defined = (
1581         mv_update_empty         => 1,
1582         restrict_allow          => 'page area var',
1583 );
1584
1585 my %o_default = (
1586         action                          => 'set',
1587         wizard_next                     => 'return',
1588         help_anchor                     => 'help',
1589         wizard_cancel           => 'back',
1590         across                          => 1,
1591         color_success           => '#00FF00',
1592         color_fail                      => '#FF0000',
1593         spacer_height           => 1,
1594         border_height           => 1,
1595         clear_image                     => 'bg.gif',
1596         table_width                     => '100%',
1597 );
1598
1599 # Build maps for ui_te_* option pass
1600 my @cgi_opts = qw/
1601
1602         append
1603         check
1604         database
1605         default
1606         disabled
1607         extra
1608         field
1609         filter
1610         height
1611         help
1612         help_url
1613         label
1614         lookup
1615         options
1616         outboard
1617         override
1618         passed
1619         pre_filter
1620         prepend
1621         template
1622         widget
1623         width
1624
1625 /;
1626
1627 my @hmap;
1628
1629 for(@cgi_opts) {
1630         push @hmap, [ qr/ui_te_$_:/, $_ ];
1631 }
1632
1633 my %ignore_cgi = qw/
1634                                         item_id                         1
1635                                         item_id_left            1
1636                                         mv_pc                           1
1637                                         mv_action           1
1638                                         mv_todo             1
1639                                         mv_ui               1
1640                                         mv_data_table       1
1641                                         mv_session_id       1
1642                                         mv_nextpage         1
1643                                         ui_sequence_edit        1
1644                                   /;
1645 sub save_cgi {
1646         my $ref = {};
1647         my @k; 
1648         if($CGI::values{save_cgi}) {
1649                 @k = split /\0/, $CGI::values{save_cgi};
1650         }
1651         else {
1652                 @k = grep ! $ignore_cgi{$_}, keys %CGI::values;
1653         }
1654
1655         # Can be an array because of produce_hidden
1656         $ref->{save_cgi} = \@k;
1657
1658         for(@k) {
1659                 $ref->{$_} = $CGI::values{$_};
1660         }
1661         return $ref;
1662 }
1663
1664 sub produce_hidden {
1665         my ($key, $val) = @_;
1666         return unless length $val;
1667         my @p; # pieces of var
1668         my @o; # output
1669         if(ref($val) eq 'ARRAY') {
1670                 @p = @$val;
1671         }
1672         else {
1673                 @p = split /\0/, $val;
1674         }
1675         for(@p) {
1676                 s/"/&quot;/g;
1677                 push @o, qq{<input type="hidden" name="$key" value="$_">\n};
1678         }
1679         return join "", @o;
1680 }
1681
1682 sub resolve_options {
1683         my ($opt, $CGI, $data) = @_;
1684
1685         # This may be passed by the caller, but is normally from the form
1686         # or URL
1687         $CGI ||= \%CGI::values;
1688
1689         my $table       = $opt->{mv_data_table};
1690         my $key         = $opt->{item_id};
1691
1692         $table = $CGI->{mv_data_table}
1693                 if ! $table and $opt->{cgi} and $CGI->{mv_data_table};
1694
1695         $opt->{table} = $opt->{mv_data_table} = $table;
1696
1697         # First we see if something has created a big options munge
1698         # for us
1699         if($opt->{all_opts}) {
1700 #::logDebug("all_opts being brought in...=" . ::uneval($opt->{all_opts}));
1701                 if(ref($opt->{all_opts}) eq 'HASH') {
1702 #::logDebug("all_opts being brought in...");
1703                         my $o = $opt->{all_opts};
1704                         for (keys %$o ) {
1705                                 $opt->{$_} = $o->{$_};
1706                         }
1707                 }
1708                 else {
1709                         my $o = meta_record($opt->{all_opts});
1710 #::logDebug("all_opts being brought in text, o=$o");
1711                         if($o) {
1712                                 for (keys %$o ) {
1713                                         $opt->{$_} = $o->{$_};
1714                                 }
1715                         }
1716                         else {
1717                                 logError("%s: improper option %s, must be %s, was %s.",
1718                                                         'table_editor',
1719                                                         'all_opts',
1720                                                         'hash',
1721                                                         ref $opt->{all_opts},
1722                                                         );
1723                         }
1724                 }
1725 #::logDebug("options now=" . ::uneval($opt));
1726         }
1727
1728         my @mapdirect = qw/
1729         back_button_class
1730         back_button_style
1731         border_cell_class
1732         border_height
1733         bottom_buttons
1734         break_cell_class
1735         break_cell_style
1736         break_row_class
1737         break_row_style
1738         button_delete
1739         cancel_button_class
1740         cancel_button_style
1741         clear_image
1742         data_cell_class
1743         data_cell_style
1744         data_row_class
1745         data_row_style
1746         default_widget
1747         delete_button_class
1748         delete_button_style
1749         display_type
1750         file_upload
1751         help_anchor
1752         help_cell_class
1753         help_cell_style
1754         image_meta
1755         include_before
1756         include_form
1757         include_form_expand
1758         include_form_interpolate
1759         intro_text
1760         label_cell_class
1761         label_cell_style
1762         left_width
1763         link_auto_number
1764         link_before
1765         link_blank_auto
1766         link_extra
1767         link_fields
1768         link_key
1769         link_label
1770         link_no_blank
1771         link_row_qual
1772         link_rows_blank
1773         link_sort
1774         link_table
1775         link_template
1776         link_view
1777         mv_auto_export
1778         mv_blob_field
1779         mv_blob_label
1780         mv_blob_nick
1781         mv_blob_pointer
1782         mv_blob_title
1783         mv_data_decode
1784         mv_data_table
1785         mv_update_empty
1786         next_button_class
1787         next_button_style
1788         no_meta
1789         nodelete
1790         ok_button_class
1791         ok_button_style
1792         output_map
1793         panel_class
1794         panel_height
1795         panel_id
1796         panel_shade
1797         panel_style
1798         panel_width
1799         reset_button_class
1800         reset_button_style
1801         restrict_allow
1802         spacer_height
1803         spacer_row_class
1804         spacer_row_style
1805         start_at
1806         tab_bgcolor_template
1807         tab_cellpadding
1808         tab_cellspacing
1809         tab_class
1810         tab_height
1811         tab_horiz_offset
1812         tab_style
1813         tab_vert_offset
1814         tab_width
1815         tabbed
1816         table_height
1817         table_width
1818         title_row_class
1819         title_row_style
1820         top_buttons
1821         ui_break_before
1822         ui_break_before_label
1823         ui_data_fields
1824         ui_data_fields_all
1825         ui_data_key_name
1826         ui_delete_box
1827         ui_display_only
1828         ui_hide_key
1829         ui_meta_specific
1830         ui_meta_view
1831         ui_new_item
1832         ui_nextpage
1833         ui_no_meta_display
1834         ui_profile
1835         view_from
1836         widget_cell_class
1837         widget_cell_style
1838         widget_class
1839                 xhtml
1840         /;
1841
1842         if($opt->{cgi}) {
1843                 for(@mapdirect) {
1844                         next if ! defined $CGI->{$_};
1845                         $opt->{$_} = $CGI->{$_};
1846                 }
1847                 my @cgi = keys %{$CGI};
1848                 foreach my $row (@hmap) {
1849                         my @keys = grep $_ =~ $row->[0], @cgi;
1850                         for(@keys) {
1851                                 /^ui_\w+:(\S+)/
1852                                         and $opt->{$row->[1]}{$1} = $CGI->{$_};
1853                         }
1854                 }
1855         }
1856
1857 #::logDebug("no_meta_display=$opt->{ui_no_meta_display}");
1858         my $tmeta;
1859         if($opt->{no_table_meta} || $opt->{ui_no_meta_display}) {
1860                 $tmeta = {};
1861         }
1862         else {
1863                 $tmeta = meta_record($table, $opt->{ui_meta_view}) || {};
1864         }
1865
1866         $opt->{view_from} ||= $tmeta->{view_from};
1867         
1868         my $baseopt;
1869         if($opt->{no_base_meta} || $opt->{ui_no_meta_display}) {
1870                 $baseopt = {};
1871         }
1872         else {
1873                 $baseopt = meta_record('table-editor') || {};
1874                 delete $baseopt->{extended};
1875         }
1876
1877         if( !   $opt->{ui_meta_view}
1878                 and $opt->{view_from}
1879                 and $data
1880                 and ! $opt->{ui_no_meta_display}
1881                 and $opt->{ui_meta_view} = $data->{$opt->{view_from}}
1882                 )
1883         {
1884                 $tmeta = meta_record($table, $opt->{ui_meta_view}) || {};
1885         }
1886
1887         # This section checks the passed options and converts them from
1888         # strings to refs if necessary
1889         FORMATS: {
1890                 no strict 'refs';
1891                 my $ref;
1892                 for(qw/
1893                     append
1894                     default
1895                     disabled
1896                                         database
1897                     error
1898                     extra
1899                     field
1900                     filter
1901                                         form
1902                     height
1903                     help
1904                     help_url
1905                     label
1906                     lookup
1907                     lookup_query
1908                                         js_check
1909                     maxlength
1910                     meta
1911                     options
1912                     outboard
1913                     override
1914                     passed
1915                     pre_filter
1916                     prepend
1917                     template
1918                     wid_href
1919                     widget
1920                     width
1921                                 / )
1922                 {
1923                         next if ref $opt->{$_};
1924                         ($opt->{$_} = {}, next) if ! $opt->{$_};
1925                         my $ref = {};
1926                         my $string = $opt->{$_};
1927                         $string =~ s/^\s+//gm;
1928                         $string =~ s/\s+$//gm;
1929                         while($string =~ m/^(.+?)=\s*(.+)/mg) {
1930                                 $ref->{$1} = $2;
1931                         }
1932                         $opt->{$_} = $ref;
1933                 }
1934         }
1935
1936         for(grep length($baseopt->{$_}), @mapdirect) {
1937 #::logDebug("checking baseopt->{$_}, baseopt=$baseopt->{$_} tmeta=$tmeta->{$_}");
1938                 $tmeta->{$_} = $baseopt->{$_}   unless length $tmeta->{$_};
1939         }
1940
1941         for(grep defined $tmeta->{$_}, @mapdirect) {
1942 #::logDebug("checking tmeta->{$_}, tmeta=$tmeta->{$_} opt=$opt->{$_}");
1943 #::logDebug("opt->{$_} is " . (defined $opt->{$_} ? 'defined' : 'undefined'));
1944                 $opt->{$_} = $tmeta->{$_}               unless defined $opt->{$_};
1945         }
1946
1947         if($opt->{cgi}) {
1948                 my @extra = qw/
1949                                 item_id
1950                                 item_id_left
1951                                 ui_clone_id
1952                                 ui_clone_tables
1953                                 ui_sequence_edit
1954                 /;
1955                 for(@extra) {
1956                         next if ! defined $CGI->{$_};
1957                         $opt->{$_} = $CGI->{$_};
1958                 }
1959         }
1960
1961         if($opt->{wizard}) {
1962                 $opt->{noexport} = 1;
1963                 $opt->{next_text} = 'Next -->' unless $opt->{next_text};
1964                 $opt->{back_text} = '<-- Back' unless $opt->{back_text};
1965         }
1966         else {
1967                 $opt->{next_text} = "Ok" unless $opt->{next_text};
1968         }
1969         $opt->{cancel_text} = 'Cancel' unless $opt->{cancel_text};
1970
1971         for(qw/ next_text cancel_text back_text/ ) {
1972                 $opt->{$_} = errmsg($opt->{$_});
1973         }
1974
1975         if($opt->{wizard} || $opt->{notable} and ! $opt->{table}) {
1976                 $opt->{table} = 'mv_null';
1977                 $Vend::Database{mv_null} = 
1978                         bless [
1979                                         {},
1980                                         undef,
1981                                         [ 'code', 'value' ],
1982                                         [ 'code' => 0, 'value' => 1 ],
1983                                         0,
1984                                         { },
1985                                         ], 'Vend::Table::InMemory';
1986         }
1987
1988         # resolve form defaults
1989
1990         while( my ($k, $v) = each %o_default_var) {
1991                 $opt->{$k} ||= $::Variable->{$v};
1992         }
1993
1994         while( my ($k, $v) = each %o_default_length) {
1995                 $opt->{$k} = $v if ! length($opt->{$k});
1996         }
1997
1998         while( my ($k, $v) = each %o_default_defined) {
1999                 $opt->{$k} = $v if ! defined($opt->{$k});
2000         }
2001
2002         while( my ($k, $v) = each %o_default) {
2003                 $opt->{$k} ||= $v;
2004         }
2005
2006         if (! $opt->{inner_table_width}) {
2007                 if ($opt->{table_width} =~ /^\d+$/) {
2008                         $opt->{inner_table_width} = $opt->{table_width} - 2;
2009                 }
2010                 elsif($opt->{table_width} =~ /\%/) {
2011                         $opt->{inner_table_width} = '100%';
2012                 }
2013                 else {
2014                         $opt->{inner_table_width} = $opt->{table_width};
2015                 }
2016         }
2017
2018         if (! $opt->{inner_table_height}) {
2019                 if ($opt->{table_height} =~ /^\d+$/) {
2020                         $opt->{inner_table_height} = $opt->{table_height} - 2;
2021                 }
2022                 elsif($opt->{table_height} =~ /\%/) {
2023                         $opt->{inner_table_height} = '100%';
2024                 }
2025                 else {
2026                         $opt->{inner_table_height} = $opt->{table_height};
2027                 }
2028         }
2029
2030         if(! $opt->{left_width}) {
2031                 if($opt->{table_width} eq '100%') {
2032                         $opt->{left_width} = 150;
2033                 }
2034                 else {
2035                         $opt->{left_width} = '30%';
2036                 }
2037         }
2038
2039         if(my $dt = $opt->{display_type}) {
2040                 my $sub = $Display_options{$dt};
2041                 $sub and ref($sub) eq 'CODE' and $sub->($opt);
2042         }
2043
2044         # init the row styles
2045         foreach my $rtype (qw/data break combo spacer title/) {
2046                 my $mainp = $rtype . '_row_extra';
2047                 my $thing = '';
2048                 for my $ptype (qw/class style align valign width/) {
2049                         my $parm = $rtype . '_row_' . $ptype;
2050                         $opt->{$parm} ||= $tmeta->{$parm};
2051                         if(defined $opt->{$parm}) {
2052                                 $thing .= qq{ $ptype="$opt->{$parm}"};
2053                         }
2054                 }
2055                 $opt->{$mainp} ||= $tmeta->{$mainp};
2056                 if($opt->{$mainp}) {
2057                         $thing .= " " . $opt->{$mainp};
2058                 }
2059                 $opt->{$mainp} = $thing;
2060         }
2061
2062         # Init the cell styles
2063
2064         for my $ctype (qw/label data widget help break/) {
2065                 my $mainp = $ctype . '_cell_extra';
2066                 my $thing = '';
2067                 for my $ptype (qw/class style align valign width/) {
2068                         my $parm = $ctype . '_cell_' . $ptype;
2069                         $opt->{$parm} ||= $tmeta->{$parm};
2070                         if(defined $opt->{$parm}) {
2071                                 $thing .= qq{ $ptype="$opt->{$parm}"};
2072                         }
2073                 }
2074                 $opt->{$mainp} ||= $tmeta->{$mainp};
2075                 if($opt->{$mainp}) {
2076                         $thing .= " " . $opt->{$mainp};
2077                 }
2078                 $opt->{$mainp} = $thing;
2079         }
2080
2081         # Init the button styles
2082
2083         for my $ctype (qw/ok next back cancel delete reset/) {
2084                 my $mainp = $ctype . '_button_extra';
2085                 my $thing = '';
2086                 for my $ptype (qw/class style/) {
2087                         my $parm = $ctype . '_button_' . $ptype;
2088                         $opt->{$parm} ||= $tmeta->{$parm};
2089                         if(defined $opt->{$parm}) {
2090                                 $thing .= qq{ $ptype="$opt->{$parm}"};
2091                         }
2092                 }
2093                 $opt->{$mainp} ||= $tmeta->{$mainp};
2094                 if($opt->{$mainp}) {
2095                         $thing .= " " . $opt->{$mainp};
2096                 }
2097                 $opt->{$mainp} = $thing;
2098         }
2099
2100         $opt->{ui_data_fields} ||= $opt->{ui_wizard_fields};
2101
2102         ###############################################################
2103         # Get the field display information including breaks and labels
2104         ###############################################################
2105         if( ! $opt->{ui_data_fields} and ! $opt->{ui_data_fields_all}) {
2106                 $opt->{ui_data_fields} = $tmeta->{ui_data_fields} || $tmeta->{options};
2107         }
2108 #::logDebug("fields were=$opt->{ui_data_fields}");
2109
2110         $opt->{ui_data_fields} =~ s/\r\n/\n/g;
2111         $opt->{ui_data_fields} =~ s/\r/\n/g;
2112         $opt->{ui_data_fields} =~ s/^[ \t]+//mg;
2113         $opt->{ui_data_fields} =~ s/[ \t]+$//mg;
2114
2115         if($opt->{ui_data_fields} =~ /\n\n/) {
2116                 my @breaks;
2117                 my @break_labels;
2118                 my $fstring = "\n\n$opt->{ui_data_fields}";
2119                 while ($fstring =~ s/\n+(?:\n[ \t]*=(.*?)(\*?))?\n+[ \t]*(\w[:.\w]+)/\n$3/) {
2120                         push @breaks, $3;
2121                         $opt->{start_at} ||= $3 if $2;
2122                         push @break_labels, "$3=$1" if $1;
2123                 }
2124                 $opt->{ui_break_before} = join(" ", @breaks)
2125                         if ! $opt->{ui_break_before};
2126                 $opt->{ui_break_before_label} = join(",", @break_labels)
2127                         if ! $opt->{ui_break_before_label};
2128                 while($fstring =~ s/\n(.*)[ \t]*\*/\n$1/) {
2129                         $opt->{focus_at} = $1;
2130                 }
2131                 $opt->{ui_data_fields} = $fstring;
2132         }
2133
2134         $opt->{ui_data_fields} ||= $opt->{mv_data_fields};
2135         $opt->{ui_data_fields} =~ s/^[\s,\0]+//;
2136         $opt->{ui_data_fields} =~ s/[\s,\0]+$//;
2137 #::logDebug("fields now=$opt->{ui_data_fields}");
2138
2139         #### This code is also in main editor routine, change there too!
2140         my $cells_per_span = $opt->{cell_span} || 2;
2141         #### 
2142
2143         ## Visual field layout
2144         if($opt->{ui_data_fields} =~ /[\w:.]+[ \t,]+\w+.*\n\w+/) {
2145                 my $cs = $opt->{colspan} ||= {};
2146                 my @things = split /\n/, $opt->{ui_data_fields};
2147                 my @rows;
2148                 my $max = 0;
2149                 for(@things) {
2150                         my @cols = split /[\s\0,]+/, $_;
2151                         my $cnt = scalar(@cols);
2152                         $max = $cnt if $cnt > $max;
2153                         push @rows, \@cols;
2154                 }
2155                 $opt->{across} = $max;
2156                 for(@rows) {
2157                         my $cnt = scalar(@$_);
2158                         if ($cnt < $max) {
2159                                 my $name = $_->[-1];
2160                                 $cs->{$name} = (($max - $cnt) * $cells_per_span) + 1;
2161                         }
2162                 }
2163         }
2164
2165         #### This code is also in main editor routine, change there too!
2166         my $rowdiv         = $opt->{across}    || 1;
2167         my $rowcount = 0;
2168         my $span = $rowdiv * $cells_per_span;
2169         #### 
2170
2171         # Make standard fixed rows
2172         $opt->{spacer_row} = <<EOF;
2173 <tr$opt->{spacer_row_extra}>
2174 <td colspan="$span" $opt->{spacer_row_extra}><img src="$opt->{clear_image}" width="1" height="$opt->{spacer_height}" alt="x"></td>
2175 </tr>
2176 EOF
2177
2178         $opt->{mv_nextpage} = $Global::Variable->{MV_PAGE}
2179                 if ! $opt->{mv_nextpage};
2180
2181         $opt->{form_extra} =~ s/^\s*/ /
2182                 if $opt->{form_extra};
2183         $opt->{form_extra} ||= '';
2184
2185         $opt->{form_extra} .= qq{ name="$opt->{form_name}"}
2186                 if $opt->{form_name};
2187
2188         $opt->{form_extra} .= qq{ target="$opt->{form_target}"}
2189                 if $opt->{form_target};
2190
2191         $opt->{enctype} = $opt->{file_upload} ? ' enctype="multipart/form-data"' : '';
2192
2193 }
2194 # UserTag table-editor Order mv_data_table item_id
2195 # UserTag table-editor addAttr
2196 # UserTag table-editor AttrAlias clone ui_clone_id
2197 # UserTag table-editor AttrAlias table mv_data_table
2198 # UserTag table-editor AttrAlias fields ui_data_fields
2199 # UserTag table-editor AttrAlias mv_data_fields ui_data_fields
2200 # UserTag table-editor AttrAlias key   item_id
2201 # UserTag table-editor AttrAlias view  ui_meta_view
2202 # UserTag table-editor AttrAlias profile ui_profile
2203 # UserTag table-editor AttrAlias email_fields ui_display_only
2204 # UserTag table-editor hasEndTag
2205 # UserTag table-editor MapRoutine Vend::Table::Editor::editor
2206 sub editor {
2207
2208         my ($table, $key, $opt, $overall_template) = @_;
2209 show_times("begin table editor call item_id=$key") if $Global::ShowTimes;
2210
2211 #::logDebug("overall_template=$overall_template\nin=$opt->{overall_template}");
2212         use vars qw/$Tag/;
2213
2214         editor_init($opt);
2215
2216         my @messages;
2217         my @errors;
2218         my $pass_return_to;
2219         my $hidden = $opt->{hidden} ||= {};
2220
2221 #::logDebug("key at beginning: $key");
2222         $opt->{mv_data_table} = $table if $table;
2223         $opt->{table}             = $opt->{mv_data_table};
2224         $opt->{ui_meta_view}  ||= $CGI->{ui_meta_view} if $opt->{cgi};
2225
2226         $key ||= $opt->{item_id};
2227
2228         if($opt->{cgi}) {
2229                 $key ||= $CGI->{item_id};
2230                 unless($opt->{ui_multi_key} = $CGI->{ui_multi_key}) {
2231                         $opt->{item_id_left} ||= $CGI::values{item_id_left};
2232                         $opt->{ui_sequence_edit} ||= $CGI::values{ui_sequence_edit};
2233                 }
2234         }
2235
2236         if($opt->{ui_sequence_edit} and ! $opt->{ui_multi_key}) {
2237                 delete $opt->{ui_sequence_edit};
2238                 my $left = delete $opt->{item_id_left}; 
2239
2240                 if(! $key) {
2241 #::logDebug("No key, getting from $left");
2242                         if($left =~ s/(.*?)[\0,]// ) {
2243                                 $key = $opt->{item_id} = $1;
2244                                 $hidden->{item_id_left} = $left;
2245                                 $hidden->{ui_sequence_edit} = 1;
2246                         }
2247                         elsif($left) {
2248                                 $key = $opt->{item_id} = $left;
2249                         }
2250 #::logDebug("No key, left now $left");
2251                 }
2252                 elsif($left) {
2253 #::logDebug("Key, leaving left $left");
2254                         $hidden->{item_id_left} = $left;
2255                         $hidden->{ui_sequence_edit} = 1;
2256                 }
2257         }
2258
2259         $opt->{item_id} = $key;
2260
2261         $pass_return_to = save_cgi() if $hidden->{ui_sequence_edit};
2262
2263         my $data;
2264         my $exists;
2265         my $db;
2266         my $multikey;
2267
2268         ## Try and sneak a peek at the data so we can determine views and
2269         ## maybe some other stuff -- we definitely need table/key or a 
2270         ## clone id
2271         unless($opt->{notable}) {
2272                 # From Vend::Data
2273                 my $tab = $table || $opt->{mv_data_table} || $CGI->{mv_data_table};
2274                 my $key = $opt->{item_id} || $CGI->{item_id};
2275                 $db = database_exists_ref($tab);
2276
2277                 if($db) {
2278                         $multikey = $db->config('COMPOSITE_KEY');
2279                         if($multikey and $key !~ /\0/) {
2280                                 $key =~ s/-_NULL_-/\0/g;
2281                         }
2282                         if($opt->{ui_clone_id} and $db->record_exists($opt->{ui_clone_id})) {
2283                                 $data = $db->row_hash($opt->{ui_clone_id});
2284                         }
2285                         elsif ($key and $db->record_exists($key)) {
2286                                 $data = $db->row_hash($key);
2287                                 $exists = 1;
2288                         }
2289                         
2290                         if(! $exists and $multikey) {
2291                                 $data = {};
2292                                 eval { 
2293                                         my @inits = split /\0/, $key;
2294                                         for(@{$db->config('_Key_columns')}) {
2295                                                 $data->{$_} = shift @inits;
2296                                         }
2297                                 };
2298                         }
2299                 }
2300         }
2301
2302         my $regin = $opt->{all_opts} ? 1 : 0;
2303
2304         resolve_options($opt, undef, $data);
2305
2306         $Trailer = $opt->{xhtml} ? '/' : '';
2307         if($regin) {
2308                 ## Must reset these in case they get set from all_opts.
2309                 $hidden = $opt->{hidden};
2310         }
2311         $overall_template = $opt->{overall_template}
2312                 if $opt->{overall_template};
2313
2314         $table = $opt->{table};
2315         $key = $opt->{item_id};
2316         if($opt->{save_meta}) {
2317                 $::Scratch->{$opt->{save_meta}} = uneval($opt);
2318         }
2319 #::logDebug("key after resolve_options: $key");
2320
2321 #::logDebug("cell_span=$opt->{cell_span}");
2322         #### This code is also in resolve_options routine, change there too!
2323         my $rowdiv         = $opt->{across}    || 1;
2324         my $cells_per_span = $opt->{cell_span} || 2;
2325         my $rowcount = 0;
2326         my $span = $rowdiv * $cells_per_span;
2327         #### 
2328
2329         my $oddspan = $span - 1;
2330         my $def = $opt->{default_ref} || $::Values;
2331
2332         my $append       = $opt->{append};
2333         my $check        = $opt->{check};
2334         my $class        = $opt->{class} || {};
2335         my $database     = $opt->{database};
2336         my $default      = $opt->{default};
2337         my $disabled     = $opt->{disabled};
2338         my $error        = $opt->{error};
2339         my $extra        = $opt->{extra};
2340         my $field        = $opt->{field};
2341         my $filter       = $opt->{filter};
2342         my $form             = $opt->{form};
2343         my $height       = $opt->{height};
2344         my $help         = $opt->{help};
2345         my $help_url     = $opt->{help_url};
2346         my $label        = $opt->{label};
2347         my $wid_href     = $opt->{wid_href};
2348         my $lookup       = $opt->{lookup};
2349         my $lookup_query = $opt->{lookup_query};
2350         my $meta         = $opt->{meta};
2351         my $js_check     = $opt->{js_check};
2352         my $maxlength    = $opt->{maxlength};
2353         my $opts         = $opt->{opts};
2354         my $options      = $opt->{options};
2355         my $outboard     = $opt->{outboard};
2356         my $override     = $opt->{override};
2357         my $passed       = $opt->{passed};
2358         my $pre_filter   = $opt->{pre_filter};
2359         my $prepend      = $opt->{prepend};
2360         my $template     = $opt->{template};
2361         my $widget       = $opt->{widget};
2362         my $width        = $opt->{width};
2363         my $colspan      = $opt->{colspan} || {};
2364
2365         my $blabel = $opt->{blabel};
2366         my $elabel = $opt->{elabel};
2367         my $mlabel = '';
2368         my $hidden_all = $opt->{hidden_all} ||= {};
2369 #::logDebug("hidden_all=" . ::uneval($hidden_all));
2370         my $ntext;
2371         my $btext;
2372         my $ctext;
2373
2374         if($pass_return_to) {
2375                 delete $::Scratch->{$opt->{next_text}};
2376         }
2377         elsif (! $opt->{wizard} and ! $opt->{nosave}) {
2378                 $ntext = $Tag->return_to('click', 1);
2379                 $ctext = $ntext . "\nmv_todo=back";
2380         }
2381         else {
2382                 if($opt->{action_click}) {
2383                         $ntext = <<EOF;
2384 mv_todo=$opt->{wizard_next}
2385 ui_wizard_action=Next
2386 mv_click=$opt->{action_click}
2387 EOF
2388                 }
2389                 else {
2390                         $ntext = <<EOF;
2391 mv_todo=$opt->{wizard_next}
2392 ui_wizard_action=Next
2393 mv_click=ui_override_next
2394 EOF
2395                 }
2396                 $::Scratch->{$opt->{next_text}} = $ntext;
2397
2398                 my $hidgo = $opt->{mv_cancelpage} || $opt->{hidden}{ui_return_to} || $CGI->{return_to};
2399                 $hidgo =~ s/\0.*//s;
2400                 $ctext = $::Scratch->{$opt->{cancel_text}} = <<EOF;
2401 mv_form_profile=
2402 ui_wizard_action=Cancel
2403 mv_nextpage=$hidgo
2404 mv_todo=$opt->{wizard_cancel}
2405 EOF
2406                 if($opt->{mv_prevpage}) {
2407                         $btext = $::Scratch->{$opt->{back_text}} = <<EOF;
2408 mv_form_profile=
2409 ui_wizard_action=Back
2410 mv_nextpage=$opt->{mv_prevpage}
2411 mv_todo=$opt->{wizard_next}
2412 EOF
2413                 }
2414                 else {
2415                         delete $opt->{back_text};
2416                 }
2417         }
2418
2419         for(qw/next_text back_text cancel_text/) {
2420                 $opt->{"orig_$_"} = $opt->{$_};
2421         }
2422
2423         $::Scratch->{$opt->{next_text}}   = $ntext if $ntext;
2424         $::Scratch->{$opt->{cancel_text}} = $ctext if $ctext;
2425         $::Scratch->{$opt->{back_text}}   = $btext if $btext;
2426
2427         $opt->{next_text} = HTML::Entities::encode($opt->{next_text}, $ESCAPE_CHARS::std);
2428         $opt->{back_text} = HTML::Entities::encode($opt->{back_text}, $ESCAPE_CHARS::std);
2429         $opt->{cancel_text} = HTML::Entities::encode($opt->{cancel_text}, $ESCAPE_CHARS::std);
2430
2431         $::Scratch->{$opt->{next_text}}   = $ntext if $ntext;
2432         $::Scratch->{$opt->{cancel_text}} = $ctext if $ctext;
2433         $::Scratch->{$opt->{back_text}}   = $btext if $btext;
2434
2435         undef $opt->{tabbed} if $::Scratch->{ui_old_browser};
2436         undef $opt->{auto_secure} if $opt->{cgi};
2437
2438         ### Build the error checking
2439         my $error_show_var = 1;
2440         my $have_errors;
2441         if($opt->{ui_profile} or $check) {
2442                 $Tag->error( { all => 1 } )
2443                         unless $CGI->{mv_form_profile} or $opt->{keep_errors};
2444                 my $prof = $opt->{ui_profile} || "&update=yes\n";
2445                 if ($prof =~ s/^\*//) {
2446                         # special notation ui_profile="*whatever" means
2447                         # use automatic checklist-related profile
2448                         my $name = $prof;
2449                         $prof = $::Scratch->{"profile_$name"} || "&update=yes\n";
2450                         if ($prof) {
2451                                 $prof =~ s/^\s*(\w+)[\s=]+required\b/$1=mandatory/mg;
2452                                 for (grep /\S/, split /\n/, $prof) {
2453                                         if (/^\s*(\w+)\s*=(.+)$/) {
2454                                                 my $k = $1; my $v = $2;
2455                                                 $v =~ s/\s+$//;
2456                                                 $v =~ s/^\s+//;
2457                                                 $error->{$k} = 1;
2458                                                 $error_show_var = 0 if $v =~ /\S /;
2459                                         }
2460                                 }
2461                                 $prof = '&calc delete $Values->{step_'
2462                                           . $name
2463                                           . "}; return 1\n"
2464                                           . $prof;
2465                                 ## Un-confuse vi }
2466                                 $opt->{ui_profile_success} = "&set=step_$name 1";
2467                         }
2468                 }
2469                 my $success = $opt->{ui_profile_success};
2470                 # make sure profile so far ends with a newline so we can add more
2471                 $prof .= "\n" unless $prof =~ /\n\s*\z/;
2472                 if(ref $check) {
2473                         while ( my($k, $v) = each %$check ) {
2474                                 next unless length $v;
2475                                 $error->{$k} = 1;
2476                                 $v =~ s/\s+$//;
2477                                 $v =~ s/^\s+//;
2478                                 $v =~ s/\s+$//mg;
2479                                 $v =~ s/^\s+//mg;
2480                                 $v =~ s/^required\b/mandatory/mg;
2481                                 unless ($v =~ /^\&/m) {
2482                                         $error_show_var = 0 if $v =~ /\S /;
2483                                         $v =~ s/^/$k=/mg;
2484                                         $v =~ s/\n/\n&and\n/g;
2485                                 }
2486                                 $prof .= "$v\n";
2487                         }
2488                 }
2489                 elsif ($check) {
2490                         for (@_ = grep /\S/, split /[\s,]+/, $check) {
2491                                 $error->{$_} = 1;
2492                                 $prof .= "$_=mandatory\n";
2493                         }
2494                 }
2495
2496                 ## Enable individual widget checks
2497                 $::Scratch->{mv_individual_profile} = 1;
2498
2499                 ## Call the profile in the form
2500                 $opt->{hidden}{mv_form_profile} = 'ui_profile';
2501                 my $fail = $opt->{mv_failpage} || $Global::Variable->{MV_PAGE};
2502
2503                 # watch out for early interpolation here!
2504                 $::Scratch->{ui_profile} = <<EOF;
2505 [perl]
2506 #Debug("cancel='$opt->{orig_cancel_text}' back='$opt->{orig_back_text}' click=\$CGI->{mv_click}");
2507         my \@clicks = split /\\0/, \$CGI->{mv_click};
2508         
2509         for( qq{$opt->{orig_cancel_text}}, qq{$opt->{orig_back_text}}) {
2510 #Debug("compare is '\$_'");
2511                 next unless \$_;
2512                 my \$cancel = \$_;
2513                 for(\@clicks) {
2514 #Debug("click is '\$_'");
2515                         return if \$_ eq \$cancel; 
2516                 }
2517         }
2518         # the following should already be interpolated by the table-editor tag
2519         # before going into scratch ui_profile
2520         return <<'EOP';
2521 $prof
2522 &fail=$fail
2523 &fatal=1
2524 $success
2525 mv_form_profile=mandatory
2526 &set=mv_todo $opt->{action}
2527 EOP
2528 [/perl]
2529 EOF
2530                 $opt->{blabel} = '<span style="font-weight: normal">';
2531                 $opt->{elabel} = '</span>';
2532                 $mlabel = ($opt->{message_label} || '&nbsp;&nbsp;&nbsp;'
2533 . errmsg('<b>Bold</b> fields are required'));
2534                 $have_errors = $Tag->error( {
2535                                                                         all => 1,
2536                                                                         show_var => $error_show_var,
2537                                                                         show_error => 1,
2538                                                                         joiner => "<br$Vend::Xtrailer>",
2539                                                                         keep => 1}
2540                                                                         );
2541                 if($opt->{all_errors} and $have_errors) {
2542                         my $title = $opt->{all_errors_title} || errmsg('Errors');
2543                         my $style = $opt->{all_errors_style} || "color: $opt->{color_fail}";
2544                         my %hash = (
2545                                 title => $opt->{all_errors_title} || errmsg('Errors'),
2546                                 style => $opt->{all_errors_style} || "color: $opt->{color_fail}",
2547                                 errors => $have_errors,
2548                         );
2549                         my $tpl = $opt->{all_errors_template} || <<EOF;
2550 <p>{TITLE}:
2551 <blockquote style="{STYLE}">{ERRORS}</blockquote>
2552 </p>
2553 EOF
2554                         $mlabel .= tag_attr_list($tpl, \%hash, 'uc');
2555
2556                 }
2557         }
2558         ### end build of error checking
2559
2560         my $die = sub {
2561                 ::logError(@_);
2562                 $::Scratch->{ui_error} .= "<BR>\n" if $::Scratch->{ui_error};
2563                 $::Scratch->{ui_error} .= ::errmsg(@_);
2564                 return undef;
2565         };
2566
2567         unless($opt->{notable}) {
2568                 # From Vend::Data
2569                 $db = database_exists_ref($table)
2570                         or return $die->("table-editor: bad table '%s'", $table);
2571         }
2572
2573         $opt->{ui_data_fields} =~ s/[,\0\s]+/ /g;
2574
2575         if($opt->{ui_wizard_fields}) {
2576                 $opt->{ui_display_only} = $opt->{ui_data_fields};
2577         }
2578
2579         if(! $opt->{ui_data_fields}) {
2580                 if( $opt->{notable}) {
2581                         ::logError("table_editor: no place to get fields!");
2582                         return '';
2583                 }
2584                 else {
2585                         $opt->{ui_data_fields} = join " ", $db->columns();
2586                 }
2587         }
2588
2589         my $keycol;
2590         if($opt->{notable}) {
2591                 $keycol = $opt->{ui_data_key_name};
2592         }
2593         else {
2594                 $keycol = $opt->{ui_data_key_name} || $db->config('KEY');
2595         }
2596
2597         ###############################################################
2598
2599         my $linecount;
2600
2601         CANONCOLS: {
2602                 my (@cols, %colseen);
2603
2604                 for (split /[,\0\s]/, $opt->{ui_data_fields}) {
2605                         next if $colseen{$_}++;
2606                         push (@cols, $_);
2607                 }
2608
2609                 $opt->{ui_data_fields} = join " ", @cols;
2610
2611                 $linecount = scalar @cols;
2612         }
2613
2614         my $url = $Tag->area('ui');
2615
2616         my $key_message;
2617         if($opt->{ui_new_item} and ! $opt->{notable}) {
2618                 if( ! $db->config('_Auto_number') and ! $db->config('AUTO_SEQUENCE')) {
2619                         $db->config('AUTO_NUMBER', '000001');
2620                         $key = $db->autonumber($key);
2621                 }
2622                 else {
2623                         $key = '';
2624                         $opt->{mv_data_auto_number} = 1;
2625                         $key_message = errmsg('(new key will be assigned if left blank)');
2626                 }
2627         }
2628
2629         if($opt->{notable}) {
2630                 $data = {};
2631         }
2632         elsif($opt->{ui_clone_id} and $db->record_exists($opt->{ui_clone_id})) {
2633                 $data = $db->row_hash($opt->{ui_clone_id})
2634                         or
2635                         return $die->('table-editor: row_hash function failed for %s.', $key);
2636                 $data->{$keycol} = $key;
2637         }
2638         elsif ($db->record_exists($key)) {
2639                 $data = $db->row_hash($key);
2640                 $exists = 1;
2641         }
2642
2643         if ($opt->{reload} and $have_errors) {
2644                 if($data) {
2645                         for(keys %$data) {
2646                                 $data->{$_} = $CGI->{$_}
2647                                         if defined $CGI->{$_};
2648                         }
2649                 }
2650                 else {
2651                         $data = { %$CGI };
2652                 }
2653         }
2654
2655
2656         my $blob_data;
2657         my $blob_widget;
2658         if($opt->{mailto} and $opt->{mv_blob_field}) {
2659                 $opt->{hidden}{mv_blob_only} = 1;
2660                 $opt->{hidden}{mv_blob_nick}
2661                         = $opt->{mv_blob_nick}
2662                         || POSIX::strftime("%Y%m%d%H%M%S", localtime());
2663         }
2664         elsif($opt->{mv_blob_field}) {
2665 #::logDebug("checking blob");
2666
2667                 my $blob_pointer;
2668                 $blob_pointer = $data->{$opt->{mv_blob_pointer}}
2669                         if $opt->{mv_blob_pointer};
2670                 $blob_pointer ||= $opt->{mv_blob_nick};
2671                         
2672
2673                 DOBLOB: {
2674
2675                         unless ( $db->column_exists($opt->{mv_blob_field}) ) {
2676                                 push @errors, ::errmsg(
2677                                                                         "blob field %s not in database.",
2678                                                                         $opt->{mv_blob_field},
2679                                                                 );
2680                                 last DOBLOB;
2681                         }
2682
2683                         my $bstring = $data->{$opt->{mv_blob_field}};
2684
2685 #::logDebug("blob: bstring=$bstring");
2686
2687                         my $blob;
2688
2689                         if(length $bstring) {
2690                                 $blob = $Vend::Interpolate::safe_safe->reval($bstring);
2691                                 if($@) {
2692                                         push @errors, ::errmsg("error reading blob data: %s", $@);
2693                                         last DOBLOB;
2694                                 }
2695 #::logDebug("blob evals to " . ::uneval_it($blob));
2696
2697                                 if(ref($blob) !~ /HASH/) {
2698                                         push @errors, ::errmsg("blob data not a storage book.");
2699                                         undef $blob;
2700                                 }
2701                         }
2702                         else {
2703                                 $blob = {};
2704                         }
2705                         my %wid_data;
2706                         my %url_data;
2707                         my @labels = keys %$blob;
2708                         unshift @labels, '';
2709
2710                         my $extra = '';
2711                         for my $k (keys %$hidden_all) {
2712                                 my $v = $hidden_all->{$k};
2713                                 if(ref($v) eq 'ARRAY') {
2714                                         for(@$v) {
2715                                                 $extra .= "\n$k=$_";
2716                                         }
2717                                 }
2718                                 else {
2719                                         $extra .= "\n$k=$v";
2720                                 }
2721                         }
2722
2723                         for my $key (@labels) {
2724                                 my $ref;
2725                                 my $lab;
2726                                 if($key) {
2727                                         $ref = $blob->{$key};
2728                                         $lab = $ref->{$opt->{mv_blob_label} || 'name'};
2729                                 }
2730                                 else {
2731                                         $key = '';
2732                                         $lab = '--' . errmsg('none') . '--';
2733                                         $ref = {};
2734                                 }
2735                                 if($lab) {
2736                                         $lab =~ s/,/&#44/g;
2737                                         $wid_data{$key} = "$key=$key - $lab";
2738                                         next unless $key;
2739                                         $url_data{$key} = $Tag->page( {
2740                                                                                         href => $Global::Variable->{MV_PAGE},
2741                                                                                         form => "
2742                                                                                                 item_id=$opt->{item_id}
2743                                                                                                 mv_blob_nick=$key$extra
2744                                                                                         ",
2745                                                                                 });
2746                                         $url_data{$key} .= "$key - $lab</a><br$Trailer>";
2747                                 }
2748                                 else {
2749                                         $wid_data{$key} = $key;
2750                                         next unless $key;
2751                                         $url_data{$key} = $Tag->page( {
2752                                                                                         href => $Global::Variable->{MV_PAGE},
2753                                                                                         form => "
2754                                                                                                 item_id=$opt->{item_id}
2755                                                                                                 mv_blob_nick=$key$extra
2756                                                                                         ",
2757                                                                                 });
2758                                         $url_data{$key} .= "$key</a>";
2759                                 }
2760                         }
2761 #::logDebug("wid_data is " . ::uneval_it(\%wid_data));
2762                         $opt->{mv_blob_title} = "Stored settings"
2763                                 if ! $opt->{mv_blob_title};
2764                         $opt->{mv_blob_title} = errmsg($opt->{mv_blob_title});
2765
2766                         $::Scratch->{Load} = <<EOF;
2767 [return-to type=click stack=1 page="$Global::Variable->{MV_PAGE}"]
2768 ui_nextpage=
2769 [perl]Log("tried to go to $Global::Variable->{MV_PAGE}"); return[/perl]
2770 mv_todo=back
2771 EOF
2772 #::logDebug("blob_pointer=$blob_pointer blob_nick=$opt->{mv_blob_nick}");
2773
2774                         my $loaded_from;
2775                         my $lfrom_msg;
2776                         if( $opt->{mv_blob_nick} ) {
2777                                 $lfrom_msg = $opt->{mv_blob_nick};
2778                         }
2779                         else {
2780                                 $lfrom_msg = errmsg("current values");
2781                         }
2782                         $lfrom_msg = errmsg("loaded from %s", $lfrom_msg);
2783                         $loaded_from = <<EOF;
2784 <i>($lfrom_msg)</i><br>
2785 EOF
2786                         if(@labels) {
2787                                 $loaded_from .= errmsg("Load from") . ":<blockquote>";
2788                                 $loaded_from .=  join (" ", @url_data{ sort keys %url_data });
2789                                 $loaded_from .= "</blockquote>";
2790                         }
2791
2792                         my $checked;
2793                         my $set;
2794                         if( $opt->{mv_blob_only} and $opt->{mv_blob_nick}) {
2795                                 $checked = ' CHECKED';
2796                                 $set     = $opt->{mv_blob_nick};
2797                         }
2798
2799                         unless ($opt->{nosave}) {
2800                                 $blob_widget = display(undef, undef, undef, {
2801                                                                         name => 'mv_blob_nick',
2802                                                                         type => $opt->{ui_blob_widget} || 'combo',
2803                                                                         filter => 'nullselect',
2804                                                                         value => $opt->{mv_blob_nick},
2805                                                                         passed => join (",", @wid_data{ sort keys %wid_data }) || 'default',
2806                                                                         });
2807                                 my $msg1 = errmsg('Save to');
2808                                 my $msg2 = errmsg('Save to book only');
2809                                 for (\$msg1, \$msg2) {
2810                                         $$_ =~ s/ /&nbsp;/g;
2811                                 }
2812                                 $blob_widget = <<EOF unless $opt->{ui_blob_hidden};
2813 <b>$msg1:</b> $blob_widget&nbsp;
2814 <input type="checkbox" name="mv_blob_only" class="$opt->{widget_class}" value="1"$checked>&nbsp;$msg2</small>
2815 EOF
2816                         }
2817
2818                         $blob_widget = <<EOF unless $opt->{ui_blob_hidden};
2819 <tr$opt->{data_row_extra}>
2820          <td width="$opt->{left_width}"$opt->{label_cell_extra}>
2821            <small>$opt->{mv_blob_title}<br>
2822                 $loaded_from
2823          </td>
2824          <td$opt->{data_cell_extra}>
2825                 $blob_widget&nbsp;
2826          </td>
2827 </tr>
2828
2829 <tr>
2830 <td colspan="$span"$opt->{border_cell_extra}><img src="$opt->{clear_image}" width="1" height="$opt->{border_height}" alt="x"></td>
2831 </tr>
2832 EOF
2833
2834                 if($opt->{mv_blob_nick}) {
2835                         delete $opt->{force_defaults};
2836                         my @keys = split /::/, $opt->{mv_blob_nick};
2837                         my $ref = $blob->{shift @keys};
2838                         for(@keys) {
2839                                 my $prior = $ref;
2840                                 undef $ref;
2841                                 eval {
2842                                         $ref = $prior->{$_};
2843                                 };
2844                                 last DOBLOB unless ref $ref;
2845                         }
2846                         for(keys %$ref) {
2847                                 $data->{$_} = $ref->{$_};
2848                         }
2849                 }
2850
2851                 }
2852         }
2853
2854 #::logDebug("data is: " . ::uneval($data));
2855         $data = { $keycol => $key }
2856                 if ! $data;
2857
2858         if(! $opt->{mv_data_function}) {
2859                 $opt->{mv_data_function} = $exists ? 'update' : 'insert';
2860         }
2861
2862         my $url_base = $opt->{secure} ? $Vend::Cfg->{SecureURL} : $Vend::Cfg->{VendURL};
2863
2864         if(! $opt->{href}) {
2865                 $opt->{href} = $opt->{mv_nextpage};
2866                 $opt->{hidden}{mv_ui} = 1
2867                         if $Vend::admin and ! defined $opt->{hidden}{mv_ui};
2868                 $opt->{hidden}{mv_action} = $opt->{action};
2869         }
2870
2871         $opt->{href} = "$url_base/$opt->{href}"
2872                 if $opt->{href} !~ m{^(https?:|)/};
2873
2874         $opt->{method} = $opt->{get} ? 'GET' : 'POST';
2875
2876         my $wo = $opt->{widgets_only};
2877
2878         my $restrict_begin;
2879         my $restrict_end;
2880
2881         if($opt->{reparse} and ! $opt->{promiscuous}) {
2882                 $restrict_begin = qq{[restrict allow="$opt->{restrict_allow}"]};
2883                 $restrict_end = '[/restrict]';
2884         }
2885
2886         no strict 'subs';
2887
2888         chunk ttag(), $restrict_begin;
2889
2890         chunk 'FORM_BEGIN', 'OUTPUT_MAP', <<EOF;
2891 <form method="$opt->{method}" action="$opt->{href}"$opt->{enctype}$opt->{form_extra}>
2892 EOF
2893
2894         my $prescript_marker = $#out;
2895
2896     $hidden->{mv_click}      = $opt->{process_filter};
2897     $hidden->{mv_todo}       = $opt->{action};
2898     $hidden->{mv_nextpage}   = $opt->{mv_nextpage};
2899     $hidden->{mv_data_table} = $table;
2900     $hidden->{mv_data_key}   = $keycol;
2901         if($opt->{cgi}) {
2902                 $hidden->{mv_return_table}   = $CGI->{mv_return_table} || $table;
2903         }
2904         else {
2905                 $hidden->{mv_return_table}   = $table;
2906         }
2907
2908         chunk 'HIDDEN_ALWAYS', 'OUTPUT_MAP', <<EOF;
2909 <input type="hidden" name="mv_session_id" value="$Vend::Session->{id}">
2910 <input type="hidden" name="mv_click" value="process_filter">
2911 EOF
2912
2913         my @opt_set = (qw/
2914                                                 ui_meta_specific
2915                                                 ui_hide_key
2916                                                 ui_meta_view
2917                                                 ui_new_item
2918                                                 ui_data_decode
2919                                                 mv_blob_field
2920                                                 mv_blob_label
2921                                                 mv_blob_title
2922                                                 mv_blob_pointer
2923                                                 mv_update_empty
2924                                                 mv_data_auto_number
2925                                                 mv_data_function
2926                                 /);
2927
2928         for my $k (@opt_set) {
2929                 $opt->{hidden}{$k} = $opt->{$k};
2930         }
2931
2932         if($pass_return_to) {
2933                 while( my($k, $v) = each %$pass_return_to) {
2934                         next if defined $opt->{hidden}{$k};
2935                         $opt->{hidden}{$k} = $pass_return_to->{$k};
2936                 }
2937         }
2938
2939         if($opt->{mailto}) {
2940                 $opt->{mailto} =~ s/\s+/ /g;
2941                 $::Scratch->{mv_email_enable} = $opt->{mailto};
2942                 $opt->{hidden}{mv_data_email} = 1;
2943         }
2944
2945         $Vend::Session->{ui_return_stack} ||= [];
2946
2947
2948         if($opt->{cgi} and ! $pass_return_to) {
2949                 my $r_ary = $Vend::Session->{ui_return_stack};
2950
2951 #::logDebug("ready to maybe push/pop return-to from stack, stack = " . ::uneval($r_ary));
2952                 if($CGI::values{ui_return_stack}++) {
2953                         push @$r_ary, $CGI::values{ui_return_to};
2954                         $CGI::values{ui_return_to} = $r_ary->[0];
2955                 }
2956                 elsif ($CGI::values{ui_return_to}) {
2957                         @$r_ary = ( $CGI::values{ui_return_to} ); 
2958                 }
2959                 chunk 'RETURN_TO', '', $Tag->return_to(); # unless $wo;
2960 #::logDebug("return-to stack = " . ::uneval($r_ary));
2961         }
2962
2963         if(ref $opt->{hidden} or ref $opt->{hidden_all}) {
2964                 my ($hk, $hv);
2965                 my @o;
2966
2967                 while ( ($hk, $hv) = each %$hidden ) {
2968
2969 ##if new item, get mv_nextpage from radio buttons
2970
2971                         if($opt->{ui_new_item}){
2972                                 next if $hk =~ /mv_nextpage/;
2973                                 push @o, produce_hidden($hk, $hv);
2974                         }
2975                         else {
2976                                 push @o, produce_hidden($hk, $hv);
2977                         }
2978                 }
2979                 while ( ($hk, $hv) = each %$hidden_all ) {
2980                         push @o, produce_hidden($hk, $hv);
2981                 }
2982                 chunk 'HIDDEN_USER', 'OUTPUT_MAP', join("", @o); # unless $wo;
2983         }
2984
2985         if($opt->{tabbed}) {
2986                 $opt->{table_width} ||= ($opt->{panel_width} || 800) + 10;
2987                 $opt->{table_height} ||= ($opt->{panel_height} || 600) + 10;
2988                 $opt->{inner_table_width} ||= ($opt->{panel_width} || 800);
2989                 $opt->{inner_table_height} ||= ($opt->{panel_height} || 600);
2990         }
2991         chunk ttag(), <<EOF; # unless $wo;
2992 <table class="touter" border="0" cellspacing="0" cellpadding="0" width="$opt->{table_width}" height="$opt->{table_height}">
2993 <tr>
2994   <td valign="top">
2995
2996 <table class="tinner" width="$opt->{inner_table_width}" height="$opt->{inner_table_height}" cellspacing="0" cellmargin="0" cellpadding="2" align="center" border="0">
2997 EOF
2998         chunk ttag(), 'NO_TOP OUTPUT_MAP', <<EOF; # unless $opt->{no_top} or $wo;
2999 <tr> 
3000 <td colspan="$span"$opt->{border_cell_extra}><img src="$opt->{clear_image}" width="1" height="$opt->{border_height}" alt="x"></td>
3001 </tr>
3002 EOF
3003
3004         if ($opt->{intro_text}) {
3005 #::logDebug("intro_text=$opt->{intro_text}");
3006                 chunk ttag(), <<EOF;
3007 <tr $opt->{spacer_row_extra}> 
3008         <td colspan="$span" $opt->{spacer_cell_extra}>$opt->{intro_text}</td>
3009 </tr>
3010 <tr $opt->{title_row_extra}> 
3011         <td colspan="$span" $opt->{title_cell_extra}>$::Scratch->{page_title}</td>
3012 </tr>
3013 EOF
3014         }
3015
3016         $opt->{top_buttons_rows} = 5 unless defined $opt->{top_buttons_rows};
3017
3018         #### Extra buttons
3019         my $extra_ok =  $blob_widget
3020                                                 || $opt->{output_map}
3021                                                 || $linecount >= $opt->{top_buttons_rows}
3022                                                 || defined $opt->{include_form}
3023                                                 || $mlabel;
3024         
3025         $mlabel ||= '&nbsp;';
3026         if ($extra_ok and ! $opt->{no_top} and ! $opt->{nosave}) {
3027                 if($opt->{back_text}) {
3028                   chunk ttag(), 'OUTPUT_MAP', <<EOF; # unless $wo;
3029 <tr$opt->{data_row_extra}>
3030 <td$opt->{label_cell_extra}>&nbsp;</td>
3031 <td align="left" colspan="$oddspan"$opt->{data_cell_extra}>
3032 EOF
3033                         chunk 'COMBINED_BUTTONS_TOP', 'BOTTOM_BUTTONS OUTPUT_MAP', <<EOF;
3034 <input type="submit" name="mv_click" value="$opt->{back_text}"$opt->{back_button_extra}>&nbsp;<input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}>&nbsp;<b><input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{next_button_extra}></b>
3035 <br>
3036 EOF
3037                         chunk 'MLABEL', 'OUTPUT_MAP', 'MESSAGES', $mlabel;
3038                         chunk ttag(), <<EOF;
3039         </td>
3040 </tr>
3041 $opt->{spacer_row}
3042 EOF
3043                 }
3044                 elsif ($opt->{wizard}) {
3045                         chunk ttag(), 'NO_TOP OUTPUT_MAP', <<EOF;
3046 <tr$opt->{data_row_extra}>
3047 <td$opt->{label_cell_extra}>&nbsp;</td>
3048 <td align="left" colspan="$oddspan"$opt->{data_cell_extra}>
3049 EOF
3050                         chunk 'WIZARD_BUTTONS_TOP', 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF; 
3051 <input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}>&nbsp;<b><input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{next_button_extra}></b>
3052 <br>
3053 EOF
3054                         chunk 'MLABEL', 'NO_TOP OUTPUT_MAP', 'MESSAGES', $mlabel;
3055                         chunk ttag(), 'NO_TOP OUTPUT_MAP', <<EOF;
3056         </td>
3057 </tr>
3058 $opt->{spacer_row}
3059 EOF
3060                 }
3061                 else {
3062                   chunk ttag(), 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF;
3063 <tr$opt->{data_row_extra}>
3064 <td$opt->{label_cell_extra}>&nbsp;</td>
3065 <td align="left" colspan="$oddspan"$opt->{data_cell_extra}>
3066 EOF
3067
3068                   chunk 'OK_TOP', 'NO_TOP OUTPUT_MAP', <<EOF;
3069 <input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{ok_button_extra}>
3070 EOF
3071                   chunk 'CANCEL_TOP', 'NOCANCEL BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF;
3072 &nbsp;
3073 <input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}>
3074 EOF
3075
3076                   if($opt->{show_reset}) {
3077                           chunk 'RESET_TOP', 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF;
3078 &nbsp;
3079 <input type="reset"$opt->{reset_button_extra}>
3080 EOF
3081                   }
3082
3083                         chunk 'MLABEL', 'BOTTOM_BUTTONS OUTPUT_MAP', $mlabel;
3084                         chunk ttag(), 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF;
3085         </td>
3086 </tr>
3087 $opt->{spacer_row}
3088 EOF
3089                 }
3090         }
3091
3092         chunk 'BLOB_WIDGET', $blob_widget; # unless $wo;
3093
3094           #### Extra buttons
3095
3096         if($opt->{ui_new_item} and $opt->{ui_clone_tables}) {
3097                 my @sets;
3098                 my %seen;
3099                 my @tables = split /[\s\0,]+/, $opt->{ui_clone_tables};
3100                 for(@tables) {
3101                         if(/:/) {
3102                                 push @sets, $_;
3103                         }
3104                         s/:.*//;
3105                 }
3106
3107                 my %tab_checked;
3108                 for(@tables, @sets) {
3109                         $tab_checked{$_} = 1 if s/\*$//;
3110                 }
3111
3112                 @tables = grep ! $seen{$_}++ && defined $Vend::Cfg->{Database}{$_}, @tables;
3113
3114                 my $tab = '';
3115                 my $set .= <<'EOF';
3116 [flag type=write table="_TABLES_"]
3117 [perl tables="_TABLES_"]
3118         delete $::Scratch->{clone_tables};
3119         return if ! $CGI->{ui_clone_id};
3120         return if ! $CGI->{ui_clone_tables};
3121         my $id = $CGI->{ui_clone_id};
3122
3123         my $out = "Cloning id=$id...";
3124
3125         my $new =  $CGI->{$CGI->{mv_data_key}}
3126                 or do {
3127                                 $out .= ("clone $id: no mv_data_key '$CGI->{mv_data_key}'");
3128                                 $::Scratch->{ui_message} = $out;
3129                                 return;
3130                 };
3131
3132         if($new =~ /\0/) {
3133                 $new =~ s/\0/,/g;
3134                 Log("cannot clone multiple keys '$new'.");
3135                 return;
3136         }
3137
3138         my %possible;
3139         my @possible = qw/_TABLES_/;
3140         @possible{@possible} = @possible;
3141         my @tables = grep /\S/, split /[\s,\0]+/, $CGI->{ui_clone_tables};
3142         my @sets = grep /:/, @tables;
3143         @tables = grep $_ !~ /:/, @tables;
3144         for(@tables) {
3145                 next unless $possible{$_};
3146                 my $db = $Db{$_} || Vend::Data::database_exists_ref($_);
3147                 next unless $db;
3148                 my $new = 
3149                 my $res = $db->clone_row($id, $new);
3150                 if($res) {
3151                         $out .= "cloned $id to to $new in table $_<BR>\n";
3152                 }
3153                 else {
3154                         $out .= "FAILED clone of $id to to $new in table $_<BR>\n";
3155                 }
3156         }
3157         for(@sets) {
3158                 my ($t, $col) = split /:/, $_;
3159                 my $db = $Db{$t} || Vend::Data::database_exists_ref($t) or next;
3160                 my $res = $db->clone_set($col, $id, $new);
3161                 if($res) {
3162                         $out .= "cloned $col=$id to to $col=$new in table $t<BR>\n";
3163                 }
3164                 else {
3165                         $out .= "FAILED clone of $col=$id to to $col=$new in table $t<BR>\n";
3166                 }
3167         }
3168         $::Scratch->{ui_message} = $out;
3169         return;
3170 [/perl]
3171 EOF
3172                 my $tabform = '';
3173                 @tables = grep $Tag->if_mm( { table => "$_=i" } ), @tables;
3174
3175                 for(@tables) {
3176                         my $db = Vend::Data::database_exists_ref($_)
3177                                 or next;
3178                         next unless $db->record_exists($opt->{ui_clone_id});
3179                         my $checked = $tab_checked{$_} ? ' CHECKED' : '';
3180                         $tabform .= <<EOF;
3181 <input type="checkbox" name="ui_clone_tables" value="$_"$checked> clone to <b>$_</b><br>
3182 EOF
3183                 }
3184                 for(@sets) {
3185                         my ($t, $col) = split /:/, $_;
3186                         my $checked = $tab_checked{$_} ? ' CHECKED' : '';
3187                         $tabform .= <<EOF;
3188 <input type="checkbox" name="ui_clone_tables" value="$_"$checked> clone entries of <b>$t</b> matching on <b>$col</b><br>
3189 EOF
3190                 }
3191
3192                 my $tabs = join " ", @tables;
3193                 $set =~ s/_TABLES_/$tabs/g;
3194                 $::Scratch->{clone_tables} = $set;
3195                 chunk ttag(), <<EOF; # unless $wo;
3196 <tr>
3197 <td colspan="$span"$opt->{border_cell_extra}>
3198 EOF
3199                 chunk 'CLONE_TABLES', <<EOF;
3200 $tabform<input type="hidden" name="mv_check" value="clone_tables">
3201 <input type="hidden" name="ui_clone_id" value="$opt->{ui_clone_id}">
3202 EOF
3203                 chunk ttag(), <<EOF; # unless $wo;
3204 </td>
3205 </tr>
3206 EOF
3207         }
3208
3209         chunk_alias 'TOP_OF_FORM', qw/ FORM_BEGIN /;
3210         chunk_alias 'TOP_BUTTONS', qw/
3211                                                                 COMBINED_BUTTONS_TOP
3212                                                                 WIZARD_BUTTONS_TOP
3213                                                                 OK_TOP
3214                                                                 CANCEL_TOP
3215                                                                 RESET_TOP
3216                                                                 /;
3217
3218         my %break;
3219         my %break_label;
3220         if($opt->{ui_break_before}) {
3221                 my @tmp = grep /\S/, split /[\s,\0]+/, $opt->{ui_break_before};
3222                 @break{@tmp} = @tmp;
3223                 if($opt->{ui_break_before_label}) {
3224                         @tmp = grep /\S/, split /\s*[,\0]\s*/, $opt->{ui_break_before_label};
3225                         for(@tmp) {
3226                                 my ($br, $lab) = split /\s*=\s*/, $_, 2;
3227                                 $break_label{$br} = $lab;
3228                         }
3229                 }
3230         }
3231         if(!$db and ! $opt->{notable}) {
3232                 return "<tr><td>Broken table '$table'</td></tr>";
3233         }
3234
3235         my $passed_fields = $opt->{ui_data_fields};
3236
3237         my @extra_cols;
3238         my %email_cols;
3239         my %ok_col;
3240         my @cols;
3241         my @dbcols;
3242         my %display_only;
3243
3244         if($opt->{notable}) {
3245                 @cols = split /[\s,\0]+/, $passed_fields;
3246         }
3247         else {
3248
3249         while($passed_fields =~ s/(\w+[.:]+\S+)//) {
3250                 push @extra_cols, $1;
3251         }
3252
3253         my @do = grep /\S/, split /[\0,\s]+/, $opt->{ui_display_only};
3254         for(@do) {
3255 #::logDebug("display_only: $_");
3256                 $email_cols{$_} = 1 if $opt->{mailto};
3257                 $display_only{$_} = 1;
3258                 push @extra_cols, $_;
3259         }
3260
3261                 @dbcols  = split /\s+/, $Tag->db_columns( {
3262                                                                                 name    => $table,
3263                                                                                 columns => $passed_fields,
3264                                                                                 passed_order => 1,
3265                                                                         });
3266
3267         if($opt->{ui_data_fields}) {
3268                 for(@dbcols, @extra_cols) {
3269                         unless (/^(\w+)([.:]+)(\S+)/) {
3270                                 $ok_col{$_} = 1;
3271                                 next;
3272                         }
3273                         my $t = $1;
3274                         my $s = $2;
3275                         my $c = $3;
3276                         if($s eq '.') {
3277                                 $c = $t;
3278                                 $t = $table;
3279                         }
3280                         else {
3281                                 $c =~ s/\..*//;
3282                         }
3283                         next unless $Tag->db_columns( { name    => $t, columns  => $c, });
3284                         $ok_col{$_} = 1;
3285                 }
3286         }
3287         @cols = grep $ok_col{$_}, split /\s+/, $opt->{ui_data_fields};
3288         }
3289
3290         $keycol = $cols[0] if ! $keycol;
3291
3292         if($opt->{defaults}) {
3293                 if($opt->{force_defaults}) {
3294                         $default->{$_} = $def->{$_} for @cols;
3295                 }
3296                 elsif($opt->{wizard}) {
3297                         for(@cols) {
3298                                 $default->{$_} = $def->{$_} if defined $def->{$_};
3299                         }
3300                 }
3301                 else {
3302                         for(@cols) {
3303                                 next if defined $default->{$_};
3304                                 next unless defined $def->{$_};
3305                                 $default->{$_} = $def->{$_};
3306                         }
3307                 }
3308         }
3309
3310         my $super = $Tag->if_mm('super');
3311
3312         my $refkey = $key;
3313
3314         my @data_enable = ($opt->{mv_blob_pointer}, $opt->{mv_blob_field});
3315         my @ext_enable;
3316
3317         if($opt->{left_width} and ! $opt->{label_cell_width}) {
3318                 $opt->{label_cell_extra} .= qq{ width="$opt->{left_width}"};
3319         }
3320
3321         my $show_meta;
3322         if($super and ! $opt->{no_meta}) {
3323                 $show_meta = defined $def->{ui_meta_force}
3324                                         ?  $def->{ui_meta_force}
3325                                         : $::Variable->{UI_META_LINK};
3326         }
3327
3328         if($show_meta) {
3329                 if(! $opt->{row_template} and ! $opt->{simple_row}) {
3330                         $opt->{meta_prepend} = qq{<br$Trailer><font size="1">}
3331                                 unless defined $opt->{meta_prepend};
3332
3333                         $opt->{meta_append} = '</font>'
3334                                 unless defined $opt->{meta_append};
3335                 }
3336                 else {
3337                         $opt->{meta_prepend} ||= '';
3338                         $opt->{meta_append} ||= '';
3339                 }
3340                 $opt->{meta_title} ||= errmsg('Edit field meta display info, table %s, column %s');
3341                 $opt->{meta_title_specific} ||= errmsg('Item-specific meta edit, table %s, column %s, key %s');
3342                 $opt->{meta_image_specific} ||= errmsg('specmeta.png');
3343                 $opt->{meta_image} ||= errmsg('meta.png');
3344                 $opt->{meta_image_extra} ||= 'border="0"';
3345                 $opt->{meta_anchor_specific} ||= errmsg('item-specific meta');
3346                 $opt->{meta_anchor} ||= errmsg('meta');
3347                 $opt->{meta_anchor_specific} ||= errmsg('item-specific meta');
3348                 $opt->{meta_extra} = " $opt->{meta_extra}"
3349                         if $opt->{meta_extra};
3350                 $opt->{meta_extra} ||= "";
3351                 $opt->{meta_extra} .= qq{ class="$opt->{meta_class}"}
3352                         if $opt->{meta_class};
3353                 $opt->{meta_extra} .= qq{ style="$opt->{meta_style}"}
3354                         if $opt->{meta_style};
3355         }
3356
3357         my $row_template = convert_old_template($opt->{row_template});
3358
3359 #::logDebug("display_type='$opt->{display_type}' row_template length=" . length($row_template));
3360
3361         if(! $row_template) {
3362                 $opt->{display_type} = 'simple_row' if $opt->{simple_row};
3363                 $opt->{display_type} ||= 'image_meta' if $opt->{image_meta};
3364                 my $dt = $opt->{display_type} ||= 'default';
3365
3366                 $dt =~ s/-/_/g;
3367                 $dt =~ s/\W+//g;
3368 #::logDebug("display_type=$dt");
3369                 my $sub = $Display_type{$dt};
3370                 if(ref($sub) eq 'CODE') {
3371                         $row_template = $sub->($opt, $span);
3372                 }
3373                 else {
3374                         ::logError("table-editor: display_type '%s' sub not found", $dt);
3375                         $row_template = $Display_type{default}->($opt, $span);
3376                 }
3377         }
3378
3379         $row_template =~ s/~OPT:(\w+)~/$opt->{$1}/g;
3380         $row_template =~ s/~([A-Z]+)_EXTRA~/$opt->{"\L$1\E_extra"} || $opt->{"\L$1\E_cell_extra"}/g;
3381
3382         $opt->{row_template} = $row_template;
3383
3384         $opt->{combo_template} ||= <<EOF;
3385 <tr$opt->{combo_row_extra}><td> {LABEL} </td><td>{WIDGET}</td></tr>
3386 EOF
3387
3388         $opt->{break_template} ||= <<EOF;
3389 <tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}\{FIRST?} style="$opt->{break_cell_first_style}"{/FIRST?}>{ROW}</td></tr>
3390 EOF
3391
3392         my %serialize;
3393         my %serial_data;
3394
3395         if(my $jsc = $opt->{js_changed}) {
3396                 $jsc =~ /^\w+$/
3397                         and $jsc = qq{onChange="$jsc} . q{('$$KEY$$','$$COL$$');"};
3398                 foreach my $c (@cols) {
3399                         next if $extra->{$c} =~ /\bonchange\s*=/i;
3400                         my $tpl = $jsc;
3401                         $tpl .= $extra->{$c} if length $extra->{$c};
3402                         $tpl =~ s/\$\$KEY\$\$/$key/g;
3403                         $tpl =~ s/\$\$COL\$\$/$c/g;
3404                         if ($extra->{$c} and $extra->{$c} =~ /\bonchange\s*=/i) {
3405                                 $tpl =~ s/onChange="//;
3406                                 $tpl =~ s/"\s*$/;/;
3407                                 $extra->{$c} =~ s/\b(onchange\s*=\s*["'])/$1$tpl/i;
3408                         }
3409                         else {
3410                                 $extra->{$c} = $tpl;
3411                         }
3412                 }
3413         }
3414
3415         my %link_row;
3416         my %link_before;
3417         if($opt->{link_table} and $key) {
3418 #::logDebug("In link table routines...");
3419                 my @ltable;
3420                 my @lfields;
3421                 my @lkey;
3422                 my @lview;
3423                 my @llab;
3424                 my @ltpl;
3425                 my @lnb;
3426                 my @lrq;
3427                 my @lra;
3428                 my @lrb;
3429                 my @lba;
3430                 my @lbefore;
3431                 my @lsort;
3432                 my $tcount = 1;
3433                 if(ref($opt->{link_table}) eq 'ARRAY') {
3434                         @ltable  = @{$opt->{link_table}};
3435                         @lfields = @{$opt->{link_fields}};
3436                         @lview   = @{$opt->{link_view}};
3437                         @lkey    = @{$opt->{link_key}};
3438                         @llab    = @{$opt->{link_label}};
3439                         @ltpl    = @{$opt->{link_template}};
3440                         @lnb     = @{$opt->{link_no_blank}};
3441                         @lrq     = @{$opt->{link_row_qual}};
3442                         @lra     = @{$opt->{link_auto_number}};
3443                         @lrb     = @{$opt->{link_rows_blank}};
3444                         @lba     = @{$opt->{link_blank_auto}};
3445                         @lbefore = @{$opt->{link_before}};
3446                         @lsort   = @{$opt->{link_sort}};
3447                 }
3448                 else {
3449                         @ltable  = $opt->{link_table};
3450                         @lfields = $opt->{link_fields};
3451                         @lview   = $opt->{link_view};
3452                         @lkey    = $opt->{link_key};
3453                         @llab    = $opt->{link_label};
3454                         @ltpl    = $opt->{link_template};
3455                         @lnb     = $opt->{link_no_blank};
3456                         @lrq     = $opt->{link_row_qual};
3457                         @lra     = $opt->{link_auto_number};
3458                         @lrb     = $opt->{link_rows_blank};
3459                         @lba     = $opt->{link_blank_auto};
3460                         @lbefore = $opt->{link_before};
3461                         @lsort   = $opt->{link_sort};
3462                 }
3463                 while(my $lt = shift @ltable) {
3464                         my $lf = shift @lfields;
3465                         my $lv = shift @lview;
3466                         my $lk = shift @lkey;
3467                         my $ll = shift @llab;
3468                         my $lb = shift @lbefore;
3469                         my $lnb = shift @lnb;
3470                         my $ls = shift @lsort;
3471                         my $lrq = shift @lrq;
3472                         my $lra = shift @lra;
3473                         my $lrb = shift @lrb;
3474                         my $lba = shift @lba;
3475
3476                         my $rcount = 0;
3477
3478                         $ll ||= errmsg("Settings in table %s linked by %s", $lt, $lk);
3479
3480                         my $whash = {};
3481
3482                         my $ldb = database_exists_ref($lt)
3483                                 or do {
3484                                         logError("Bad table editor link table: %s", $lt);
3485                                         next;
3486                                 };
3487
3488                         my $lmeta = $Tag->meta_record($lt, $lv);
3489                         $lf ||= $lmeta->{spread_fields};
3490
3491                         my $l_pkey = $ldb->config('KEY');
3492                         $lrq ||= $l_pkey;
3493
3494                         if($lba) {
3495                                 my @f = grep /\w/, split /[\s,\0]+/, $lf;
3496                                 @f = grep $_ ne $lk && $_ ne $l_pkey, @f;
3497                                 $lf = join " ", @f;
3498                         }
3499
3500                         my $an_piece = '';
3501                         if($lra) {
3502                                 $an_piece = <<EOF;
3503 <input type="hidden" name="mv_data_auto_number__$tcount" value="$lra">
3504 <input type="hidden" name="mv_data_function__$tcount" value="insert">
3505 EOF
3506                         }
3507
3508                         ## Have to produce two field lists -- one for
3509                         ## in link_blank_auto mode (no link_key for row_edit)
3510                         ## and one with all when not in link_blank_auto
3511
3512                         my @cf = grep /\S/, split /[\s,\0]+/, $lf;
3513                         @cf = grep $_ ne $l_pkey, @cf;
3514                         $lf = join " ", @cf;
3515
3516                         unshift @cf, $lk if $lba;
3517                         my $df = join " ", @cf;
3518
3519                         my $lextra = $opt->{link_extra} || '';
3520                         $lextra = " $lextra" if $lextra;
3521
3522                         my @lout = q{<table cellspacing="0" cellpadding="1">};
3523                         push @lout, qq{<tr><td$lextra>
3524 <input type="hidden" name="mv_data_table__$tcount" value="$lt">
3525 <input type="hidden" name="mv_data_fields__$tcount" value="$df">
3526 <input type="hidden" name="mv_data_multiple__$tcount" value="1">
3527 <input type="hidden" name="mv_data_key__$tcount" value="$l_pkey">
3528 <input type="hidden" name="mv_data_multiple_qual__$tcount" value="$lrq">
3529 $an_piece
3530 $l_pkey</td>};
3531                         push @lout, $Tag->row_edit({ table => $lt, columns => $lf });
3532                         push @lout, '</tr>';
3533
3534                         my $tname = $ldb->name();
3535                         my $k = $key;
3536                         my $lfor = $k;
3537                         $lfor = $ldb->quote($key, $lk);
3538                         my $q = "SELECT $l_pkey FROM $tname WHERE $lk = $lfor";
3539                         $q .= " ORDER BY $ls" if $ls;
3540                         my $ary = $ldb->query($q);
3541                         for(@$ary) {
3542                                 my $rk = $_->[0];
3543                                 my $pp = $rcount ? "${rcount}_" : '';
3544                                 my $hid = qq{<input type="hidden" name="$pp${l_pkey}__$tcount" value="};
3545                                 $hid .= HTML::Entities::encode($rk);
3546                                 $hid .= qq{">};
3547                                 push @lout, qq{<tr><td$lextra>$rk$hid</td>};
3548                                 if($lba) {
3549                                         my $hid = qq{<input type="hidden" name="$pp${lk}__$tcount" value="};
3550                                         $hid .= HTML::Entities::encode($k);
3551                                         $hid .= qq{">};
3552                                         push @lout, qq{<td$lextra>$k$hid</td>};
3553                                 }
3554                                 my %o = (
3555                                         table => $lt,
3556                                         key => $_->[0],
3557                                         extra => $opt->{link_extra},
3558                                         pointer => $rcount,
3559                                         stacker => $tcount,
3560                                         columns => $lf,
3561                                 );
3562                                 $rcount++;
3563                                 push @lout, $Tag->row_edit(\%o);
3564                                 push @lout, "</tr>";
3565                         }
3566
3567                         if($lba and $lrq eq $lk || $lrq eq $l_pkey) {
3568                                 my $colcount = scalar(@cf) + 1;
3569                                 push @lout, qq{<td colspan="$colcount">Link row qualifier must be different than link_key and primary code when in auto mode.</td>};
3570                                 $lnb = 1;
3571                         }
3572
3573                         unless($lnb) {
3574                                 my $start_ptr = 999000;
3575                                 $lrb ||= 1;
3576                                 for(0 .. $opt->{link_rows_blank}) {
3577                                         my %o = (
3578                                                 table => $lt,
3579                                                 blank => 1,
3580                                                 extra => $opt->{link_extra},
3581                                                 pointer => $start_ptr,
3582                                                 stacker => $tcount,
3583                                                 columns => $lf,
3584                                         );
3585                                         my $ktype = $lba ? 'hidden' : 'text';
3586                                         push @lout, qq{<tr><td$lextra>};
3587                                         push @lout, qq{<input size="8" type="$ktype" name="${start_ptr}_${l_pkey}__$tcount" value="">};
3588                                         push @lout, '(auto)' if $lba;
3589                                         push @lout, '</td>';
3590                                         if($lba) {
3591                                                 my $hid = qq{<input type="hidden" name="${start_ptr}_${lk}__$tcount" value="};
3592                                                 $hid .= HTML::Entities::encode($k);
3593                                                 $hid .= qq{">};
3594                                                 push @lout, qq{<td$lextra>$k$hid</td>};
3595                                         }
3596                                         push @lout, $Tag->row_edit(\%o);
3597                                         push @lout, '</tr>';
3598                                         $start_ptr++;
3599                                 }
3600                         }
3601                         push @lout, "</table>";
3602                         $whash->{LABEL}  = $ll;
3603                         $whash->{WIDGET} = join "", @lout;
3604                         my $murl = '';
3605                         if($show_meta) {
3606                                 my $murl;
3607                                 $murl = $Tag->area({
3608                                                         href => 'admin/db_metaconfig_spread',
3609                                                         form => qq(
3610                                                                         ui_table=$lt
3611                                                                         ui_view=$lv
3612                                                                 ),
3613                                                         });
3614                                 $whash->{META_URL} = $murl;
3615                                 $whash->{META_STRING} = qq{<a href="$murl"$opt->{meta_extra}>};
3616                                 $whash->{META_STRING} .= errmsg('meta') . '</a>';
3617
3618                         }
3619                         $whash->{ROW} = 1;
3620                         $link_row{$lt} = $whash;
3621                         if($lb) {
3622                                 $link_before{$lb} = $lt;
3623                         }
3624                         my $mde_key = "mv_data_enable__$tcount";
3625                         $::Scratch->{$mde_key} = "$lt:" . join(",", $l_pkey, @cf) . ':';
3626                         $tcount++;
3627 #::logDebug("Made link_table...whash=$whash");
3628                 }
3629         }
3630
3631         if($opt->{include_form}) {
3632 #::logDebug("In link table routines...");
3633                 my @icells;
3634                 my @ibefore;
3635                 my $tcount = 1;
3636                 my @includes;
3637                 if(ref($opt->{include_form}) eq 'ARRAY') {
3638                         @icells  = @{delete $opt->{include_form}};
3639                         @ibefore = @{delete $opt->{include_before} || []};
3640                 }
3641                 else {
3642                         @icells  = delete $opt->{include_form};
3643                         @ibefore = delete $opt->{include_before};
3644                 }
3645
3646                 $opt->{include_before} = {};
3647                 while(my $it = shift @icells) {
3648                         my $ib = shift @ibefore;
3649
3650                         my $rcount = 0;
3651
3652 #::logDebug("Made include_before=$it");
3653                         if($ib) {
3654                                 $opt->{include_before}{$ib} = $it;
3655                         }
3656                         elsif($it =~ /^\s*<tr\b/i) {
3657                                 push @includes, $it;
3658                         }
3659                         else {
3660                                 push @includes, "<tr>$it</tr>";
3661                         }
3662                 }
3663                 $opt->{include_form} = join "\n", @includes if @includes;
3664         }
3665
3666     if($opt->{tabbed}) {
3667         my $ph = $opt->{panel_height} || '600';
3668         my $pw = $opt->{panel_width} || '800';
3669         my $th = $opt->{tab_height} || '30';
3670         my $oh = $ph + $th;
3671         my $extra = qq{ width="$pw" height="$oh" valign="top"};
3672         chunk ttag(), qq{<tr><td colspan="$span"$extra>\n};
3673     }
3674
3675 #::logDebug("include_before: " . uneval($opt->{include_before}));
3676
3677         my @extra_hidden;
3678         my $icount = 0;
3679
3680         my $reload;
3681         ## Find out what our errors are
3682         if($CGI->{mv_form_profile} eq 'ui_profile' and $Vend::Session->{errors}) {
3683                 for(keys %{$Vend::Session->{errors}}) {
3684                         $error->{$_} = 1;
3685                 }
3686                 $reload = 1 unless $opt->{no_reload};
3687         }
3688
3689         my @prescript;
3690         my @postscript;
3691
3692         for(qw/callback_prescript callback_postscript/) {
3693                 next unless $opt->{$_};
3694                 next if ref($opt->{$_}) eq 'CODE';
3695                 $Tag->error({
3696                                                 name => errmsg('table-editor'), 
3697                                                 set => errmsg('%s is not a code reference', $_), 
3698                                         });
3699         }
3700
3701         my $callback_prescript = $opt->{callback_prescript} || sub {
3702                 push @prescript, @_;
3703         };
3704         my $callback_postscript = $opt->{callback_postscript} || sub {
3705                 push @postscript, @_;
3706         };
3707
3708
3709         if(my $sheet = $opt->{style_sheet}) {
3710                 $sheet =~ s/^\s+//;
3711                 $sheet =~ s/\s+$//;
3712                 if($Style_sheet{$sheet}) {
3713                         push @prescript, $Style_sheet{$sheet};
3714                 }
3715                 elsif($sheet =~ /\s/) {
3716                         my $pre_put;
3717                         if($sheet !~ /^<\w+/) {
3718                                 $pre_put = 1;
3719                                 push @prescript, q{<style type="text/css">}
3720                         }
3721                         push @prescript, $sheet;
3722                         push @prescript, q{</style>} if $pre_put;
3723                 }
3724                 else {
3725                         ::logError(
3726                                 "%s: style sheet %s not found, using default",
3727                                 errmsg('table-editor'),
3728                                 $sheet,
3729                         );
3730                         push @prescript, $Style_sheet{default};
3731                 }
3732         }
3733
3734         foreach my $col (@cols) {
3735                 my $t;
3736                 my $c;
3737                 my $k;
3738                 my $tkey_message;
3739                 if($col eq $keycol) {
3740                         if($opt->{ui_hide_key}) {
3741                                 my $kval = $key || $override->{$col} || $default->{$col};
3742                                 push @extra_hidden,
3743                                         qq{<input type="hidden" name="$col" value="$kval">};
3744                                 if($break{$col}) {
3745                                         $titles[$ctl_index] = $break_label{$col};
3746                                 }
3747                                 next;
3748                         }
3749                         elsif ($opt->{ui_new_item}) {
3750                                 $tkey_message = $key_message;
3751                         }
3752                 }
3753
3754                 my $w = '';
3755                 my $do = $display_only{$col};
3756                 
3757                 my $currval;
3758                 my $serialize;
3759
3760                 if($col =~ /(\w+):+([^:]+)(?::+(\S+))?/) {
3761                         $t = $1;
3762                         $c = $2;
3763                         $c =~ /(.+?)\.\w.*/
3764                                 and $col = "$t:$1"
3765                                         and $serialize = $c;
3766                         $k = $3 || undef;
3767                         $do = 1 if $disabled->{$c};
3768                         push @ext_enable, ("$t:$c" . $k ? ":$k" : '')
3769                                 unless $do;
3770                 }
3771                 else {
3772                         $t = $table;
3773                         $c = $col;
3774                         $c =~ /(.+?)\.\w.*/
3775                                 and $col = $1
3776                                         and $serialize = $c;
3777                         $do = 1 if $disabled->{$c};
3778                         push @data_enable, $col
3779                                 unless $do and ! $opt->{mailto};
3780                 }
3781
3782                 my $overridden;
3783
3784                 $currval = $data->{$col} if defined $data->{$col};
3785                 if ($opt->{force_defaults} or defined $override->{$c} ) {
3786                         $currval = $override->{$c};
3787                         $overridden = 1;
3788 #::logDebug("hit override for $col,currval=$currval");
3789                 }
3790                 elsif (defined $CGI->{"ui_preload:$t:$c"} ) {
3791                         $currval = delete $CGI->{"ui_preload:$t:$c"};
3792                         $overridden = 1;
3793 #::logDebug("hit preload for $col,currval=$currval");
3794                 }
3795                 elsif( ($do && ! $currval) or $col =~ /:/) {
3796                         if(defined $k) {
3797                                 my $check = $k;
3798                                 undef $k;
3799                                 for( $override, $data, $default) {
3800                                         next unless defined $_->{$check};
3801                                         $k = $_->{$check};
3802                                         last;
3803                                 }
3804                         }
3805                         else {
3806                                 $k = defined $key ? $key : $refkey;
3807                         }
3808                         $currval = tag_data($t, $c, $k) if defined $k;
3809 #::logDebug("hit display_only for $col, t=$t, c=$c, k=$k, currval=$currval");
3810                 }
3811                 elsif (defined $default->{$c} and ! length($data->{$c}) ) {
3812                         $currval = $default->{$c};
3813                         $overridden = 1;
3814 #::logDebug("hit preload for $col,currval=$currval");
3815                 }
3816                 else {
3817 #::logDebug("hit data->col for $col, t=$t, c=$c, k=$k, currval=$currval");
3818                         $currval = length($data->{$col}) ? $data->{$col} : '';
3819                         $overridden = 1;
3820                 }
3821
3822                 if($reload and defined $CGI::values{$col}) {
3823                         $currval = $CGI::values{$col};
3824                 }
3825
3826                 my $namecol;
3827                 if($serialize) {
3828 #::logDebug("serialize=$serialize");
3829                         if($serialize{$col}) {
3830                                 push @{$serialize{$col}}, $serialize;
3831                         }
3832                         else {
3833                                 my $sd;
3834                                 if($col =~ /:/) {
3835                                         my ($tt, $tc) = split /:+/, $col;
3836                                         $sd = tag_data($tt, $tc, $k);
3837                                 }
3838                                 else {
3839                                         $sd = $data->{$col} || $default->{$col};
3840                                 }
3841 #::logDebug("serial_data=$sd");
3842                                 $serial_data{$col} = $sd;
3843                                 $opt->{hidden}{$col} = $data->{$col};
3844                                 $serialize{$col} = [$serialize];
3845                         }
3846                         $c =~ /\.(.*)/;
3847                         my $hk = $1;
3848 #::logDebug("fetching serial_data for $col hk=$hk data=$serial_data{$col}");
3849                         $currval = dotted_hash($serial_data{$col}, $hk);
3850 #::logDebug("fetched hk=$hk value=$currval");
3851                         $overridden = 1;
3852                         $namecol = $c = $serialize;
3853
3854                         if($reload and defined $CGI::values{$namecol}) {
3855                                 $currval = $CGI::values{$namecol};
3856                         }
3857                 }
3858
3859                 $namecol = $col unless $namecol;
3860
3861 #::logDebug("display_only=$do col=$c");
3862                 $widget->{$c} = 'value'
3863                         if $do and ! ($disabled->{$c} || $opt->{wizard} || $opt->{mailto});
3864
3865                 if (! length $currval and defined $default->{$c}) {
3866                         $currval = $default->{$c};
3867                 }
3868
3869                 $template->{$c} ||= $row_template;
3870
3871                 my $err_string;
3872                 if($error->{$c}) {
3873                         my $parm = {
3874                                         name => $c,
3875                                         std_label => '$LABEL$',
3876                                         required => 1,
3877                                         };
3878                         if($opt->{all_errors}) {
3879                                 $parm->{keep} = 1;
3880                                 $parm->{text} = $opt->{error_template} || <<EOF;
3881 <font color="$opt->{color_fail}">\$LABEL\$ (%s)</font>
3882 [else]{REQUIRED <b>}{LABEL}{REQUIRED </b>}[/else]
3883 EOF
3884                         }
3885                         $err_string = $Tag->error($parm);
3886                         if($template->{$c} !~ /{ERROR\??}/) {
3887                                 $template->{$c} =~ s/{LABEL}/$err_string/g
3888                                         and
3889                                 $template->{$c} =~ s/\$LABEL\$/{LABEL}/g;
3890                         }
3891                 }
3892
3893                 my $meta_string = '';
3894                 my $meta_url;
3895                 my $meta_url_specific;
3896                 if($show_meta) {
3897                         # Get global variables
3898                         my $base = $::Variable->{UI_BASE}
3899                                          || $Global::Variable->{UI_BASE} || 'admin';
3900                         my $page = $Global::Variable->{MV_PAGE};
3901                         my $id = $t . "::$c";
3902                         $id = $opt->{ui_meta_view} . "::$id"
3903                                 if $opt->{ui_meta_view} and $opt->{ui_meta_view} ne 'metaconfig';
3904
3905                         my $return = <<EOF;
3906 ui_return_to=$page
3907 ui_return_to=item_id=$opt->{item_id}
3908 ui_return_to=ui_meta_view=$opt->{ui_meta_view}
3909 ui_return_to=mv_return_table=$t
3910 mv_return_table=$table
3911 ui_return_stack=$CGI->{ui_return_stack}
3912 EOF
3913
3914                         $meta_url = $Tag->area({
3915                                                                 href => "$base/meta_editor",
3916                                                                 form => qq{
3917                                                                                         item_id=$id
3918                                                                                         $return
3919                                                                                 }
3920                                                         });
3921                         my $meta_specific = '';
3922                         if($opt->{ui_meta_specific}) {
3923                                 $meta_url_specific = $Tag->area({
3924                                                                                 href => "$base/meta_editor",
3925                                                                                 form => qq{
3926                                                                                                         item_id=${t}::${c}::$key
3927                                                                                                         $return
3928                                                                                                 }
3929                                                                                 });
3930                                 $meta_specific = <<EOF;
3931 <br$Trailer><a href="$meta_url_specific"$opt->{meta_extra} tabindex="9999">$opt->{meta_anchor_specific}</a>
3932 EOF
3933                         }
3934                                                                 
3935                         $opt->{meta_append} = '</font>'
3936                                 unless defined $opt->{meta_append};
3937                         if($opt->{image_meta}) {
3938 #::logDebug("meta-title=$opt->{meta_title}");
3939                                 my $title = errmsg($opt->{meta_title}, $t, $c);
3940                                 $meta_string = <<EOF;
3941 <a href="$meta_url"$opt->{meta_extra} tabindex="9999"><img src="$opt->{meta_image}" title="$title" $opt->{meta_image_extra}></a>
3942 EOF
3943                                 if($meta_specific) {
3944                                         $title = errmsg($opt->{meta_title_specific}, $t, $c, $key);
3945                                         $meta_string .= <<EOF;
3946 <a href="$meta_url_specific"$opt->{meta_extra} tabindex="9999"><img src="$opt->{meta_image_specific}" title="$title" $opt->{meta_image_extra}></a>
3947 EOF
3948                                 }
3949                         }
3950                         else {
3951                                 $meta_string = <<EOF;
3952 $opt->{meta_prepend}<a href="$meta_url"$opt->{meta_extra} tabindex="9999">$opt->{meta_anchor}</a>
3953 $meta_specific$opt->{meta_append}
3954 EOF
3955                         }
3956                 }
3957
3958                 $class->{$c} ||= $opt->{widget_class};
3959
3960 #::logDebug("col=$c currval=$currval widget=$widget->{$c} label=$label->{$c}");
3961                 my $display = display($t, $c, $key, {
3962                                                         append                          => $append->{$c},
3963                                                         applylocale                     => 1,
3964                                                         arbitrary                       => $opt->{ui_meta_view},
3965                                                         callback_prescript  => $callback_prescript,
3966                                                         callback_postscript  => $callback_postscript,
3967                                                         class                           => $class->{$c},
3968                                                         column                          => $c,
3969                                                         db                                      => $database->{$c},
3970                                                         default                         => $currval,
3971                                                         default_widget          => $opt->{default_widget},
3972                                                         disabled                        => $disabled->{$c},
3973                                                         extra                           => $extra->{$c},
3974                                                         fallback                        => 1,
3975                                                         field                           => $field->{$c},
3976                                                         filter                          => $filter->{$c},
3977                                                         form                            => $form->{$c},
3978                                                         form_name                       => $opt->{form_name},
3979                                                         height                          => $height->{$c},
3980                                                         help                            => $help->{$c},
3981                                                         help_url                        => $help_url->{$c},
3982                                                         href                            => $wid_href->{$c},
3983                                                         js_check                        => $js_check->{$c},
3984                                                         key                                     => $key,
3985                                                         label                           => $label->{$c},
3986                                                         lookup                          => $lookup->{$c},
3987                                                         lookup_query            => $lookup_query->{$c},
3988                                                         maxlength                       => $maxlength->{$c},
3989                                                         meta                            => $meta->{$c},
3990                                                         meta_url                        => $meta_url,
3991                                                         meta_url_specific       => $meta_url_specific,
3992                                                         name                            => $namecol,
3993                                                         options                         => $options->{$c},
3994                                                         outboard                        => $outboard->{$c},
3995                                                         override                        => $overridden,
3996                                                         opts                            => $opts->{$c},
3997                                                         passed                          => $passed->{$c},
3998                                                         pre_filter                      => $pre_filter->{$c},
3999                                                         prepend                         => $prepend->{$c},
4000                                                         return_hash                     => 1,
4001                                                         restrict_allow      => $opt->{restrict_allow},
4002                                                         specific                        => $opt->{ui_meta_specific},
4003                                                         table                           => $t,
4004                                                         type                            => $widget->{$c},
4005                                                         ui_no_meta_display      => $opt->{ui_no_meta_display},
4006                                                         width                           => $width->{$c},
4007                                                 });
4008 #::logDebug("finished display of col=$c");
4009                 my $update_ctl;
4010
4011                 if ($display->{WIDGET} =~ /^\s*<input\s[^>]*type\s*=\W*hidden\b[^>]*>\s*$/is) {
4012                         push @extra_hidden, $display->{WIDGET};
4013                         next;
4014                 }
4015                 $display->{TEMPLATE} = $template->{$c};
4016                 $display->{META_STRING} = $meta_string;
4017                 $display->{TKEY}   = $tkey_message;
4018                 $display->{BLABEL} = $blabel;
4019                 $display->{ELABEL} = $elabel;
4020                 $display->{COLSPAN} = qq{ colspan="$colspan->{$namecol}"}
4021                         if $colspan->{$namecol};
4022                 $display->{ERROR}  = $err_string;
4023
4024                 $update_ctl = 0;
4025                 if ($break{$namecol}) {
4026 #::logDebug("breaking on $namecol, control index=$ctl_index");
4027                         if(@controls == 0 and @titles == 0) {
4028                                 $titles[0] = $break_label{$namecol};
4029                         }
4030                         elsif(@titles == 0) {
4031                                 $titles[1] = $break_label{$namecol};
4032                                 $update_ctl = 1;
4033                         }
4034                         else {
4035                                 push @titles, $break_label{$namecol};
4036                                 $update_ctl = 1;
4037                         }
4038                 }
4039                 if($link_before{$col}) {
4040                         col_chunk "_SPREAD_$link_before{$col}",
4041                                                 delete $link_row{$link_before{$col}};
4042                 }
4043                 if($opt->{include_before} and $opt->{include_before}{$col}) {
4044 #::logDebug("include_before: $col $opt->{include_before}{$col}");
4045                         my $chunk = delete $opt->{include_before}{$col};
4046                         if($opt->{include_form_interpolate}) {
4047                                 $Vend::Interpolate::Tmp->{table_editor_data} = $data;
4048 #::logDebug("data to include=" . ::uneval($data));
4049                                 $chunk = interpolate_html($chunk);
4050                         }
4051                         elsif($opt->{include_form_expand}) {
4052                                 $chunk = expand_values($chunk);
4053 #::logDebug("include_before: expanded values on $col $chunk");
4054                         }
4055                         my $h = { ROW => $chunk };
4056                         $h->{TEMPLATE} = $opt->{whole_template} || '<tr>{ROW}</tr>';
4057                         col_chunk "_INCLUDE_$col", $h;
4058                 }
4059                 $ctl_index++ if $update_ctl;
4060                 if($opt->{start_at} and $opt->{start_at} eq $namecol) {
4061                         $opt->{start_at_index} = $ctl_index;
4062 #::logDebug("set start_at_index to $ctl_index");
4063                 }
4064 #::logDebug("control index now=$ctl_index");
4065                 col_chunk $namecol, $display;
4066         }
4067
4068         for(sort keys %link_row) {
4069 #::logDebug("chunking link_table to _SPREAD_$_");
4070                 col_chunk "_SPREAD_$_", delete $link_row{$_};
4071         }
4072
4073         my $firstout = scalar(@out);
4074
4075         if($opt->{tabbed}) {
4076                 chunk ttag(), qq{</td></tr>\n};
4077         }
4078
4079         while($rowcount % $rowdiv) {
4080                 chunk ttag(), '<td colspan="$cells_per_span">&nbsp;</td>'; # unless $wo;
4081                 $rowcount++;
4082         }
4083
4084         $::Scratch->{mv_data_enable} = '';
4085         if($opt->{auto_secure}) {
4086                 $::Scratch->{mv_data_enable} .= "$table:" . join(",", @data_enable) . ':';
4087                 $::Scratch->{mv_data_enable} .=  $opt->{item_id};
4088                 $::Scratch->{mv_data_enable_key} = $opt->{item_id};
4089         }
4090         if(@ext_enable) {
4091                 $::Scratch->{mv_data_enable} .= " " . join(" ", @ext_enable) . " ";
4092         }
4093 #::logDebug("setting mv_data_enable to $::Scratch->{mv_data_enable}");
4094         my @serial = keys %serialize;
4095         my @serial_fields;
4096         my @o;
4097         for (@serial) {
4098 #::logDebug("$_ serial_data=$serial_data{$_}");
4099                 $serial_data{$_} = uneval($serial_data{$_})
4100                         if is_hash($serial_data{$_});
4101                 $serial_data{$_} =~ s/\&/&amp;/g;
4102                 $serial_data{$_} =~ s/"/&quot;/g;
4103                 push @o, qq{<input type="hidden" name="$_" value="$serial_data{$_}">}; # unless $wo;
4104                 push @serial_fields, @{$serialize{$_}};
4105         }
4106
4107         if(! $wo and @serial_fields) {
4108                 push @o, qq{<input type="hidden" name="ui_serial_fields" value="};
4109                 push @o, join " ", @serial_fields;
4110                 push @o, qq{">};
4111                 chunk 'HIDDEN_SERIAL', 'OUTPUT_MAP', join("", @o);
4112         }
4113
4114         ###
4115         ### Here the user can include some extra stuff in the form....
4116         ###
4117         if($opt->{include_form}) {
4118                 my $chunk = delete $opt->{include_form};
4119                 if($opt->{include_form_interpolate}) {
4120                         $chunk = interpolate_html($chunk);
4121                 }
4122                 elsif($opt->{include_form_expand}) {
4123                         $chunk = expand_values($chunk);
4124                 }
4125                 col_chunk '_INCLUDE_FORM',
4126                                         {
4127                                                 ROW => $chunk,
4128                                                 TEMPLATE => $opt->{whole_template} || '<tr>{ROW}</tr>',
4129                                         };
4130         }
4131         ### END USER INCLUDE
4132
4133         unless ($opt->{mailto} and $opt->{mv_blob_only}) {
4134                 @cols = grep ! $display_only{$_} && ! $disabled->{$_}, @cols;
4135         }
4136         $passed_fields = join " ", @cols;
4137
4138         my ($beghid, $endhid) = split m{</td>}i, $opt->{spacer_row}, 2;
4139         $endhid = "</td>$endhid" if $endhid;
4140         chunk ttag(), 'OUTPUT_MAP', $beghid;
4141         chunk 'HIDDEN_EXTRA', 'OUTPUT_MAP', qq{<input type="hidden" name="mv_data_fields" value="$passed_fields">@extra_hidden};
4142         chunk ttag(), 'OUTPUT_MAP', $endhid;
4143
4144   SAVEWIDGETS: {
4145         last SAVEWIDGETS if $wo || $opt->{nosave}; 
4146 #::logDebug("in SAVEWIDGETS");
4147                 chunk ttag(), 'OUTPUT_MAP', <<EOF;
4148 <tr$opt->{data_row_extra}>
4149 <td$opt->{label_cell_extra}>&nbsp;</td>
4150 <td align="left" colspan="$oddspan"$opt->{data_cell_extra}>
4151 EOF
4152
4153                 if($opt->{back_text}) {
4154
4155                         chunk 'COMBINED_BUTTONS_BOTTOM', 'OUTPUT_MAP', <<EOF;
4156 <input type="submit" name="mv_click" value="$opt->{back_text}"$opt->{back_button_extra}>&nbsp;<input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}>&nbsp;<b><input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{next_button_extra}></b>
4157 EOF
4158                 }
4159                 elsif($opt->{wizard}) {
4160                         chunk 'WIZARD_BUTTONS_BOTTOM', 'OUTPUT_MAP', <<EOF;
4161 <input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}>&nbsp;<b><input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{next_button_extra}></b>
4162 EOF
4163                 }
4164                 else {
4165                         chunk 'OK_BOTTOM', 'OUTPUT_MAP', <<EOF;
4166 <input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{ok_button_extra}>
4167 EOF
4168
4169                         chunk 'CANCEL_BOTTOM', 'NOCANCEL OUTPUT_MAP', <<EOF;
4170 &nbsp;<input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}>
4171 EOF
4172
4173                         chunk 'RESET_BOTTOM', 'OUTPUT_MAP', qq{&nbsp;<input type="reset"$opt->{reset_button_extra}>}
4174                                 if $opt->{show_reset};
4175                 }
4176
4177
4178         if($exists and ! $opt->{nodelete} and $Tag->if_mm('tables', "$table=d")) {
4179                 my $key_display = join '/', split /\0/, $key;
4180                 my $extra = $Tag->return_to( { type => 'click', tablehack => 1 });
4181                 my $page = $CGI->{ui_return_to};
4182                 $page =~ s/\0.*//s;
4183                 my $url = $Tag->area( {
4184                                         href => $page,
4185                                         form => qq!
4186                                                 deleterecords=1
4187                                                 ui_delete_id=$key
4188                                                 mv_data_table=$table
4189                                                 mv_click=db_maintenance
4190                                                 mv_action=back
4191                                                 $extra
4192                                         !,
4193                                         });
4194                 my $delstr = errmsg('Delete');
4195                 my $delmsg = errmsg('Are you sure you want to delete %s?', $key_display);
4196                 if($opt->{output_map} or $opt->{button_delete}) {
4197                         chunk 'DELETE_BUTTON', 'NOSAVE OUTPUT_MAP', <<EOF;
4198 &nbsp;
4199         <input
4200                 type=button
4201                 onClick="if(confirm('$delmsg')) { location='$url' }"
4202                 title="Delete $key_display"
4203                 value="$delstr"$opt->{delete_button_extra}>
4204 EOF
4205                 }
4206                 else {
4207                         chunk 'DELETE_BUTTON', 'NOSAVE OUTPUT_MAP', <<EOF; # if ! $opt->{nosave};
4208 <br><br><a onClick="return confirm('$delmsg')" href="$url"><img src="delete.gif" alt="Delete $key_display" border="0"></a> $delstr
4209 EOF
4210                 }
4211
4212         }
4213
4214         if(! $opt->{notable} and $Tag->if_mm('tables', "$table=x") and ! $db->config('LARGE') ) {
4215                 my $checked = ' CHECKED';
4216                 my $msg = errmsg("Automatically export to text file");
4217                 $checked = ''
4218                         if defined $opt->{mv_auto_export} and ! $opt->{mv_auto_export};
4219                 my $autoexpstr = errmsg('Auto-export');         
4220                 chunk 'AUTO_EXPORT', 'NOEXPORT NOSAVE OUTPUT_MAP', <<EOF;
4221 <small>
4222 &nbsp;
4223 &nbsp;
4224         <input type="checkbox" class="$opt->{widget_class}" title="$msg" name="mv_auto_export" value="$table"$checked><span class="$opt->{widget_class}" title="$msg">&nbsp;$autoexpstr</span>
4225 EOF
4226
4227         }
4228
4229         if($opt->{ui_new_item}) {
4230                 my $aa_msg = l('Add another item');
4231                 my $rt_msg = l('Return to table select');
4232                 chunk 'DO_ANOTHER', 'OUTPUT_MAP', $opt->{ui_do_another} || <<EOF;
4233 <small>
4234 &nbsp;
4235         <input type="radio" class="$opt->{widget_class}" name="mv_nextpage" value="admin/flex_select" CHECKED>
4236         $rt_msg
4237         <input type="radio" class="$opt->{widget_class}" name="mv_nextpage" value="admin/flex_editor">
4238         $aa_msg
4239 EOF
4240
4241         }
4242
4243         chunk_alias 'HIDDEN_FIELDS', qw/
4244                                                                                 HIDDEN_ALWAYS
4245                                                                                 HIDDEN_EXTRA
4246                                                                                 HIDDEN_SERIAL
4247                                                                                 HIDDEN_USER
4248                                                                                 /;
4249         chunk_alias 'BOTTOM_BUTTONS', qw/
4250                                                                                 WIZARD_BUTTONS_BOTTOM
4251                                                                                 COMBINED_BUTTONS_BOTTOM
4252                                                                                 OK_BOTTOM
4253                                                                                 CANCEL_BOTTOM
4254                                                                                 RESET_BOTTOM
4255                                                                                 DO_ANOTHER
4256                                                                                 /;
4257         chunk_alias 'EXTRA_BUTTONS', qw/
4258                                                                                 AUTO_EXPORT
4259                                                                                 DELETE_BUTTON
4260                                                                                 /;
4261         chunk ttag(), 'OUTPUT_MAP', <<EOF;
4262 </small>
4263 </td>
4264 </tr>
4265 EOF
4266   } # end SAVEWIDGETS
4267
4268         my $message = '';
4269
4270         if(@errors) {
4271                 $message .= '<p>Errors:';
4272                 $message .= qq{<font color="$opt->{color_fail}">};
4273                 $message .= '<blockquote>';
4274                 $message .= join "<br>", @errors;
4275                 $message .= '</blockquote></font>';
4276         }
4277         if(@messages) {
4278                 $message .= '<p>Messages:';
4279                 $message .= qq{<font color="$opt->{color_success}">};
4280                 $message .= '<blockquote>';
4281                 $message .= join "<br>", @messages;
4282                 $message .= '</blockquote></font>';
4283         }
4284         $Tag->error( { all => 1 } );
4285
4286         chunk ttag(), 'NO_BOTTOM _MESSAGE', <<EOF;
4287 <tr>
4288         <td colspan=$span$opt->{border_cell_extra}>
4289 EOF
4290
4291         chunk 'MESSAGE_TEXT', 'NO_BOTTOM', $message; # unless $wo or ($opt->{no_bottom} and ! $message);
4292
4293         chunk ttag(), 'NO_BOTTOM _MESSAGE', <<EOF;
4294         </td>
4295 </tr>
4296 EOF
4297
4298 #::logDebug("tcount=$tcount_all, prior to closing table");
4299         chunk ttag(), <<EOF; # unless $wo;
4300 <tr> 
4301 <td colspan="$span"$opt->{border_cell_extra}><img src="$opt->{clear_image}" width="1" height="$opt->{border_height}" alt="x"></td>
4302 </tr>
4303 </table>
4304 </td></tr></table>
4305 EOF
4306
4307         my $end_script = '';
4308         if( $opt->{start_at} || $opt->{focus_at}
4309                         and
4310                 $opt->{form_name}
4311                         and
4312                 $widget->{$opt->{start_at}} !~ /radio|check/i
4313                 )
4314         {
4315                 my $foc = $opt->{focus_at} || $opt->{start_at};
4316                 $end_script = <<EOF;
4317 <script>
4318         document.$opt->{form_name}.$foc.focus();
4319 </script>
4320 EOF
4321         }
4322
4323         if($opt->{adjust_cell_class}) {
4324                 $end_script .= <<EOF;
4325 <script>
4326         var mytags=document.getElementsByTagName("td");
4327
4328         var max = 0;
4329         var nextmax = 0;
4330         var type = '$opt->{adjust_cell_class}';
4331         for(var i = 0; i < mytags.length; i++) {
4332                 if(mytags[i].getAttribute('class') != type)
4333                         continue;
4334                 var wid = mytags[i].offsetWidth;
4335                 var span = mytags[i].getAttribute('colspan');
4336                 if(span < 2 && mytags[i].getAttribute('class') == type && wid >= max) {
4337                         nextmax = max;
4338                         max = wid;
4339         }
4340         }
4341         if(max > 500 && (max / 2) > nextmax) 
4342                 max = nextmax;
4343         for(var i = 0; i < mytags.length; i++) {
4344                 if(mytags[i].getAttribute('class') != type)
4345                         continue;
4346                 if(mytags[i].getAttribute('colspan') > 1)
4347                         continue;
4348                 mytags[i].setAttribute('width', max);
4349         }
4350 </script>
4351 EOF
4352         }
4353
4354         if(@prescript) {
4355                 my $end = $outhash{FORM_BEGIN};
4356                 $outhash{FORM_BEGIN} = join "\n", $end, @prescript;
4357         }
4358
4359         unshift @postscript, qq{</form>$end_script};
4360
4361         chunk 'FORM_END', 'OUTPUT_MAP', join("\n", @postscript);
4362
4363         chunk ttag(), $restrict_end;
4364
4365         chunk_alias 'BOTTOM_OF_FORM', qw/ FORM_END /;
4366
4367         my %ehash = (
4368         );
4369         for(qw/
4370                 BOTTOM_BUTTONS
4371                 NOCANCEL
4372                 NOEXPORT
4373                 NOSAVE
4374                 NO_BOTTOM
4375                 OUTPUT_MAP
4376                 NO_TOP
4377                 SHOW_RESET
4378                 /)
4379         {
4380                 $ehash{$_} = $opt->{lc $_} ? 1 : 0;
4381         }
4382
4383         $ehash{MESSAGE} = length($message) ? 1 : 0;
4384
4385 #::logDebug("exclude is " . uneval(\%exclude));
4386
4387         if($opt->{output_map}) {
4388                 $opt->{output_map} =~ s/^\s+//;
4389                 $opt->{output_map} =~ s/\s+$//;
4390                 my %map;
4391                 my @map = split /[\s,=\0]+/, $opt->{output_map};
4392                 if(@map > 1) {
4393                         for(my $i = 0; $i <= @map; $i += 2) {
4394                                 $map{ uc $map[$i] } = lc $map[$i + 1];
4395                         }
4396                 }
4397                 else {
4398                         %map = qw/
4399                                 TOP_OF_FORM                     top_of_form
4400                                 BOTTOM_OF_FORM          bottom_of_form
4401                                 HIDDEN_FIELDS       hidden_fields
4402                                 TOP_BUTTONS         top_buttons
4403                                 BOTTOM_BUTTONS          bottom_buttons
4404                                 EXTRA_BUTTONS           extra_buttons
4405                         /;
4406                 }
4407
4408                 while(my($al, $to) = each %map) {
4409 #::logDebug("outputting alias $al to output $to");
4410                         my $ary = $alias{$al} || [];
4411 #::logDebug("alias $al means " . join(" ", @$ary));
4412                         my $string = join("", @outhash{@$ary});
4413 #::logDebug("alias $al string is $string");
4414                         $Tag->output_to($to, { name => $to}, $string );
4415                 }
4416         }
4417
4418         resolve_exclude(\%ehash);
4419
4420         if($wo) {
4421                 return (map { @$_ } @controls) if wantarray;
4422                 return join "", map { @$_ } @controls;
4423         }
4424 show_times("end table editor call item_id=$key") if $Global::ShowTimes;
4425
4426         my @put;
4427         if($overall_template =~ /\S/) {
4428                 my $death = sub {
4429                         my $item = shift;
4430                         logDebug("must have chunk {$item} defined in overall template.");
4431                         logError("must have chunk {%s} defined in overall template.", $item);
4432                         return undef;
4433                 };
4434
4435                 if($opt->{fields_template_only}) {
4436                         my $tstart = '<table';
4437                         for my $p (qw/height width cellspacing cellmargin cellpadding class style/) {
4438                                 my $tag = "table_$p";
4439                                 next unless length $opt->{$tag} and $opt->{$tag} =~ /\S/;
4440                                 my $val = HTML::Entities::encode($opt->{$tag});
4441                                 $tstart .= qq{ $p="$val"};
4442                         }
4443                         $tstart .= ">";
4444                         $overall_template = qq({TOP_OF_FORM}
4445 {HIDDEN_FIELDS}
4446 $tstart
4447 <tr><td colspan="$span">{TOP_BUTTONS}</td></tr>
4448 <tr><td colspan="$span">$overall_template</td></tr>
4449 <tr><td colspan="$span">{BOTTOM_BUTTONS}</td></tr>
4450 </table>
4451 {BOTTOM_OF_FORM}
4452 );
4453                 }
4454
4455                 unless($opt->{incomplete_form_ok}) {
4456                         $overall_template =~ /{TOP_OF_FORM}/
4457                                 or return $death->('TOP_OF_FORM');
4458                         $overall_template =~ /{HIDDEN_FIELDS}/
4459                                 or return $death->('HIDDEN_FIELDS');
4460                         $overall_template =~ /{BOTTOM_OF_FORM}/
4461                                 or return $death->('BOTTOM_OF_FORM');
4462                 }
4463
4464                 while($overall_template =~ m/\{((?:_INCLUDE_|COLUMN_|_SPREAD_).*?)\}/g) {
4465                         my $name = $1;
4466                         my $orig = $name;
4467                         my $thing = delete $outhash{$name};
4468 #::logDebug("Got to column replace $name, thing=$thing");
4469                         if($name =~ /^_/) {
4470                                 $overall_template =~ s/\{$name\}/$thing->{ROW}/;
4471                         }
4472                         elsif($name =~ s/__WIDGET$//) {
4473                                 $thing = delete $outhash{$name};
4474                                 my $lab  = "${name}__LABEL";
4475                                 my $help = "${name}__HELP";
4476 #::logDebug("Got to widget replace $name, thing=$thing");
4477                                 $overall_template =~ s/\{$lab\}/$thing->{LABEL}/;
4478                                 $overall_template =~ s/\{$help\}/$thing->{HELP}/;
4479                                 $overall_template =~ s/\{$orig\}/$thing->{WIDGET}/;
4480                         }
4481                         elsif($thing) {
4482                                 $overall_template =~ s!\{$name\}!
4483                                                                                 tag_attr_list($thing->{TEMPLATE}, $thing)
4484                                                                                 !e;
4485                         }
4486                 }
4487                 while($overall_template =~ m/\{([A-Z_]+)\}/g) {
4488                         my $name = $1;
4489                         my $thing = delete $outhash{$name};
4490 #::logDebug("Got to random replace $name, thing=$thing");
4491                         next if ! $thing and $alias{$name};
4492                         $overall_template =~ s/\{$name\}/$thing/;
4493                 }
4494                 while($overall_template =~ m/\{([A-Z_]+)\}/g) {
4495                         my $name = $1;
4496                         my $thing = delete $alias{$name};
4497 #::logDebug("Got to alias replace $name, thing=$thing");
4498                         $overall_template =~ s/\{$name\}/join "", @outhash{@$thing}/e;
4499                 }
4500                 my @put;
4501                 if($opt->{tabbed}) {
4502                         my @tabcont;
4503                         for(@controls) {
4504                                 push @tabcont, create_rows($opt, $_);
4505                         }
4506                         $opt->{panel_table_extra} ||= 'width="100%" cellpadding="3" cellspacing="1"';
4507                         $opt->{panel_table_extra} =~ s/^/ /;
4508                         $opt->{panel_prepend} ||= "<table$opt->{panel_table_extra}>";
4509                         $opt->{panel_append} ||= '</table>';
4510                         push @put, tabbed_display(\@titles,\@tabcont,$opt);
4511                 }
4512                 else {
4513                         my $first = 0;
4514                         for(my $i = 0; $i < @controls; $i++) {
4515                                 push @put, tag_attr_list(
4516                                                                 $opt->{break_template},
4517                                                                 { FIRST => ! $first++, ROW => $titles[$i] },
4518                                                         )
4519                                         if $titles[$i];
4520                                 push @put, create_rows($opt, $controls[$i]);
4521                         }
4522                 }
4523                 $overall_template =~ s/{:REST}/join "\n", @put/e;
4524 #::logDebug("overall_template:\n$overall_template");
4525                 return $overall_template;
4526         }
4527
4528         for(my $i = 0; $i < $firstout; $i++) {
4529 #::logDebug("$out[$i] content length=" . length($outhash{$out[$i]} ));
4530                 push @put, $outhash{$out[$i]};
4531         }
4532
4533         if($opt->{tabbed}) {
4534 #::logDebug("In tabbed display...controls=" . scalar(@controls) . ", titles=" . scalar(@titles));
4535                 my @tabcont;
4536                 for(@controls) {
4537                         push @tabcont, create_rows($opt, $_);
4538                 }
4539                 $opt->{panel_table_extra} ||= 'width="100%" cellpadding="3" cellspacing="1"';
4540                 $opt->{panel_table_extra} =~ s/^/ /;
4541                 $opt->{panel_prepend} ||= "<table$opt->{panel_table_extra}>";
4542                 $opt->{panel_append} ||= '</table>';
4543                 push @put, tabbed_display(\@titles,\@tabcont,$opt);
4544         }
4545         else {
4546 #::logDebug("titles=" . uneval(\@titles) . "\ncontrols=" . uneval(\@controls));
4547                 my $first = 0;
4548                 for(my $i = 0; $i < @controls; $i++) {
4549                                 push @put, tag_attr_list(
4550                                                                 $opt->{break_template},
4551                                                                 { FIRST => ! $first++, ROW => $titles[$i] },
4552                                                         )
4553                                         if $titles[$i];
4554                         push @put, create_rows($opt, $controls[$i]);
4555                 }
4556         }
4557
4558         for(my $i = $firstout; $i < @out; $i++) {
4559 #::logDebug("$out[$i] content length=" . length($outhash{$out[$i]} ));
4560                 push @put, @outhash{$out[$i]};
4561         }
4562         return join "", @put;
4563 }
4564
4565 sub convert_old_template {
4566         my $string = shift;
4567         $string =~ s/\$WIDGET\$/{WIDGET}/g
4568                 or return $string;
4569         $string =~ s!\{HELP_URL\}(.*)\{/HELP_URL\}!{HELP_URL?}$1\{/HELP_URL?}!gs;
4570         $string =~ s/\$HELP\$/{HELP}/g;
4571         $string =~ s/\$HELP_URL\$/{HELP_URL}/g;
4572         $string =~ s/\~META\~/{META_STRING}/g;
4573         $string =~ s/\$LABEL\$/{LABEL}/g;
4574         $string =~ s/\~ERROR\~/{LABEL}/g;
4575         $string =~ s/\~TKEY\~/{TKEY}/g;
4576         $string =~ s/\~BLABEL\~/{BLABEL}/g;
4577         $string =~ s/\~ELABEL\~/{ELABEL}/g;
4578         return $string;
4579 }
4580
4581 sub create_rows {
4582         my ($opt, $columns) = @_;
4583         $columns ||= [];
4584
4585         my $rowdiv                      = $opt->{across}    || 1;
4586         my $cells_per_span      = $opt->{cell_span} || 2;
4587         my $rowcount            = 0;
4588         my $span                        = $rowdiv * $cells_per_span;
4589         my $oddspan                     = $span - 1;
4590         my $colspan = $opt->{colspan};
4591 #::logDebug("colspan=" . ::uneval($colspan));
4592
4593         my @out;
4594
4595         for my $c (@$columns) {
4596                 my $colname = $c;
4597                 $colname =~ s/^COLUMN_//;
4598 #::logDebug("doing column $c name=$colname");
4599                 # If doesn't exist, was brought in before.
4600                 my $ref = delete $outhash{$c}
4601                         or next;
4602                 if($ref->{ROW}) {
4603 #::logDebug("outputting ROW $c=$ref->{ROW}");
4604                         my $tpl = $ref->{TEMPLATE} || $opt->{combo_template};
4605                         push @out, tag_attr_list($tpl, $ref);
4606                         $rowcount = 0;
4607                         next;
4608                 }
4609                 my $w = '';
4610                 $w .= "<tr$opt->{data_row_extra}>\n" unless $rowcount++ % $rowdiv;
4611                 if(my $s = $colspan->{$colname}) {
4612 #::logDebug("found colspan=$s (ref=$ref->{COLSPAN}) for $colname");
4613                         my $extra =  ($s - 1) / $cells_per_span;
4614                         $rowcount += $extra;
4615                 }
4616                 $w .= tag_attr_list($ref->{TEMPLATE}, $ref);
4617                 $w .= "</tr>" unless $rowcount % $rowdiv;
4618                 push @out, $w;
4619         }       
4620
4621         if($rowcount % $rowdiv) {
4622                 my $w = '';
4623                 while($rowcount % $rowdiv) {
4624                         $w .= '<td colspan="$cells_per_span">&nbsp;</td>';
4625                         $rowcount++;
4626                 }
4627                 $w .= "</tr>";
4628                 push @out, $w;
4629         }
4630         return join "\n", @out;
4631 }
4632
4633 1;