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