[interchange-cvs] interchange - heins modified code/UI_Tag/table_editor_tpl.coretag

[email protected] [email protected]
Thu Apr 4 16:47:01 2002


User:      heins
Date:      2002-04-04 21:46:39 GMT
Added:     code/UI_Tag table_editor_tpl.coretag
Log:
	* Add templated version of table-editor which may eventually takke
	  over from the other version (much more testing needed).

	  Allows templated table editor for better control of form
	  presentation.

	  A default matching the current table-editor is provided if
	  no template is given.

	  Example:

		[table-editor-tpl cgi=1]
		[restrict]
		[editors]
		<FORM METHOD="[editor-param method]" ACTION="[editor-param href]"
			[editor-param form_name]
			[editor-param enctype]
			[editor-param form_extra]
			>
		<INPUT TYPE=hidden NAME=mv_todo VALUE="[editor-param action]">
		<INPUT TYPE=hidden NAME=mv_click VALUE="process_filter">
		[editor-param tpl_sidstr]
		[editor-param tpl_clone]
		[editor-param tpl_hidden]
		[editor-param tpl_return_to]
		<table class=touter cellspacing=0 cellpadding=0 width="[editor-param table_width]">
		<tr>
		  <td>

		[columns]
		[column-list]
		<tr class=rnorm>
		   <td class=clabel width="[editor-param left_width]">
			 [editor-param label_prepend][column-param label][editor-param label_append][column-param meta]
		   </td>
		   <td class=cdata>
			 <table cellspacing=0 cellmargin=0 width="100%">
			   <tr>
				 <td class=cwidget>
				   [column-param widget]
				 </td>
				 <td class=chelp>[column-param tkey]
					[if-column-param help]
						<i>[column-param help]</i>
					[/if-column-param]
					[if-column-param help_url]
					<BR><A HREF="[column-param help_url]">help</A>
					[/if-column-param]
				</td>
			   </tr>
			 </table>
		   </td>
		</tr>
		[/column-list]
		[/columns]
		<tr class=rtitle>
		<td align=right colspan="[editor-param calc_span]"><img src="[editor-param clear_image]" width=1 height=3 alt=x></td>
		</tr>
		[editor-param tpl_bottom_buttons]

		<tr class=rspacer>
		<td colspan="[editor-param calc_span]"><img src="[editor-param clear_image]" height=3 alt=x></td>
				</tr>
			</table>
			</td>
		</tr>
		</table>

		</form>
		[/editors]
		[/restrict]
			[/table-editor-tpl]

Revision  Changes    Path
1.1                  interchange/code/UI_Tag/table_editor_tpl.coretag


rev 1.1, prev_rev 1.0
Index: table_editor_tpl.coretag
===================================================================
UserTag table-editor-tpl Order mv_data_table item_id
UserTag table-editor-tpl addAttr
UserTag table-editor-tpl hasEndTag
UserTag table-editor-tpl AttrAlias clone ui_clone_id
UserTag table-editor-tpl AttrAlias table mv_data_table
UserTag table-editor-tpl AttrAlias fields ui_data_fields
UserTag table-editor-tpl AttrAlias mv_data_fields ui_data_fields
UserTag table-editor-tpl AttrAlias key   item_id
UserTag table-editor-tpl AttrAlias view  ui_meta_view
UserTag table-editor-tpl AttrAlias profile ui_profile
UserTag table-editor-tpl AttrAlias email_fields ui_display_only
#UserTag table-editor Documentation <<EOD
#=head1 NAME
#
#[table-editor-tpl]
#
#=head1 SYNOPSIS
#
#  [table-editor-tpl
#  		table=ic_table
#		cgi=1*
#		item-id="key"
#		across=n*
#		noexport=1*
# 
#		wizard=1*
#		next_text='Next -->'*
#		cancel_text='Cancel'*
#		back_text='<-- Back'*
# 
#		hidden.formvarname="value"
#
#		item_id_left="keys remaining"
#		mv_blob_field=column*
#		mv_blob_nick=name*
#		mv_blob_pointer="current name"*
#		mv_blob_label="Label text"
#		mv_blob_title="Title HTML"
#
#		ui_break_before="field1 field2"
#		ui_break_before_label="field1=Label 1, field2=Label 2"
#		ui_data_fields="field1 field2 fieldn ..."*
#		ui_data_fields_all=1*
#		ui_display_only="no_set_field"*
#		ui_hide_key=1*
#		ui_meta_specific=1*
#		ui_meta_view="viewname"
#		ui_nextpage="next_destination"
#		ui_prevpage="back_destination"
#		ui_return_to="cancel_destination"
#		ui_new_item=1*
#		ui_sequence_edit=1*
#		ui_clone_id="key"
#		ui_clone_tables="table1 table2 ..."
#		ui_delete_box=1*
#		mv_update_empty=0*
# 
#		widget.field="select|text|any ic widget"
#		label.field="Field Label"
#		help.field="Help text"
#		help-url.field="http://url/to/more/help"
#		default.field="preset value"*
#		override.field="forced value"*
#		filter.field="filter1 filter2"
#		pre-filter.field="filter1 filter2"
#		error.field=1*
#		height.field=N
#		width.field=N
#		passed.field="val1=Label 1, val2=Label 2"
#		lookup.field="lookup_field"
#		database.field="table"
#		field.field="column"
#		outboard.field="key"
#		append.field="HTML"
#		prepend.field="HTML"
#
#	]
#
#=head1 DESCRIPTION
#
#The [table-editor] tag produces an HTML form that edits a database
#table or collects values for a "wizard". It is extremely configurable
#as to display and characteristics of the widgets used to collect the
#input.
#
#The widget types are based on the Interchange C<[display ...]> UserTag,
#which in turn is heavily based on the ITL core C<[accessories ...]> tag.
#
#The C<simplest> form of C<[table-editor]> is:
#
#	[table-editor table=foo]
#
#A page which contains only that tag will edit the table C<foo>, where
#C<foo> is the name of an Interchange table to edit. If no C<foo> table
#is C<defined>, then nothing will be displayed.
#
#If the C<mv_metadata> entry "foo" is present, it is used as the
#definition for table display, including the fields to edit and labels
#for sections of the form. If C<ui_data_fields> is defined, this
#cancels fetch of the view and any breaks and labels must be
#defined with C<ui_break_before> and C<ui_break_before_label>. More
#on the view concept later.
#
#A simple "wizard" can be made with:
#
#	[table-editor
#			wizard=1
#			ui_wizard_fields="foo bar"
#			mv_nextpage=wizard2
#			mv_prevpage=wizard_intro
#			]
#
#The purpose of a "wizard" is to collect values from the user and
#place them in the $Values array. A next page value (option mv_nextpage)
#must be defined to give a destination; if mv_prevpage is defined then
#a "Back" button is presented to allow paging backward in the wizard.
#
#EOD

UserTag table-editor-tpl Routine <<EOR

my $Default_template = <<'EOT';
[restrict]
[editors]
<FORM METHOD="[editor-param method]" ACTION="[editor-param href]"
	[editor-param form_name]
	[editor-param enctype]
	[editor-param form_extra]
	>
[editor-param tpl_sidstr]
<INPUT TYPE=hidden NAME=mv_todo VALUE="[editor-param action]">
<INPUT TYPE=hidden NAME=mv_click VALUE="process_filter">
[editor-param tpl_hidden]
[editor-param tpl_return_to]
<table class=touter cellspacing=0 cellpadding=0 width="[editor-param table_width]">
<tr>
  <td>

[if-editor-param !no_top]
<table class=tinner  width="[editor-param inner_table_width]" cellspacing=0 cellmargin=0 width="100%" cellpadding=2 align=center border=0>
<tr class=rtitle> 
<td align=right colspan="[editor-param calc_span]"><img src="[editor-param clear_image]" width=1 height=3 alt=x></td>
</tr>
[editor-param tpl_top_buttons]
[editor-param tpl_blob_widget]
[/if-editor-param]
[editor-param tpl_clone]
[columns]
[column-list]
<tr class=rnorm>
   <td class=clabel width="[editor-param left_width]"> 
     [editor-param label_prepend][column-param label][editor-param label_append][column-param meta]
   </td>
   <td class=cdata> 
     <table cellspacing=0 cellmargin=0 width="100%">
       <tr> 
         <td class=cwidget> 
           [column-param widget]
         </td>
         <td class=chelp>[column-param tkey]
		 	[if-column-param help]
				<i>[column-param help]</i>
		 	[/if-column-param]
		 	[if-column-param help_url]
			<BR><A HREF="[column-param help_url]">help</A>
		 	[/if-column-param]
		</td>
       </tr>
     </table>
   </td>
</tr>
[/column-list]
[/columns]
<tr class=rtitle> 
<td align=right colspan="[editor-param calc_span]"><img src="[editor-param clear_image]" width=1 height=3 alt=x></td>
</tr>
[if-editor-param tpl_include_form]
[editor-param tpl_include_form]
<td align=right colspan="[editor-param calc_span]"><img src="[editor-param clear_image]" width=1 height=3 alt=x></td>
[/if-editor-param]
[editor-param tpl_bottom_buttons]

[if-editor-param tpl_errors]
<tr class=rspacer>
	<td colspan="[editor-param calc_span]">
		Errors: 
		<blockquote>
			[editor-param tpl_errors]
		</blockquote>
	</td>
</tr>
[/if-editor-param]

[if-editor-param tpl_messages]
<tr class=rspacer>
	<td colspan="[editor-param calc_span]">
		Errors: 
		<blockquote>
			[editor-param tpl_errors]
		</blockquote>
	</td>
</tr>
[/if-editor-param]

<tr class=rspacer>
<td colspan="[editor-param calc_span]"><img src="[editor-param clear_image]" height=3 alt=x></td>
		</tr>
	</table>
	</td>
</tr>
</table>

</form>
[/editors]
[/restrict]
EOT

sub editor_region {
	my ($opt, $opt_out, $hidden) = @_;

	my @hidden;
	while( my($k, $v) = each %$hidden) {
		HTML::Entities::encode($k);
		HTML::Entities::encode($v);
		push @hidden, qq{<input type="hidden" name="$k" value="$v">};
	}
	$opt_out->{tpl_hidden} = join "\n", @hidden;

	return ['editors', 'editor', [ $opt_out ] ];
}

sub column_region {
	my ($opt, $opt_out, $controls) = @_;
	return ['columns', 'column', $controls];
}

sub run_templates {
	my ($opt, $to_run, $bref) = @_;
#::logDebug("te_array=" . ::uneval(\@_));

	my %todo = ( editor => 1, columns => 1 );
	my $region;
	my $prefix;
	my $ary;
	my @things = @$to_run;
	while( $region = shift  @things ) {
		$prefix = shift @things;
		$ary    = shift @things;

		delete $todo{$region};
::logDebug("run_template region=$region prefix=$prefix ary=$ary from=$opt->{from_session}");
		$region =~ s/[-_]/[-_]/g;

		next unless $$bref =~ m{\[$region\](.*?)\[/$region\]}is;
		my $run = $1;
		
		if( $run !~ /\S/ or (! $ary and $run !~ /no[-_]match\]/i) ) {
			$$bref =~ s{\[$region\](.*?)\[/$region\]}{}sgi;
			next;
		}
		$opt->{prefix} = $prefix;
		$opt->{object} = {
							mv_results => $ary,
							matches => scalar(@$ary),
							mv_matchlimit => $opt->{ml} || 100,
						};
		$$bref =~ s{\[$region\](.*?)\[/$region\]}
				   {Vend::Interpolate::region($opt, $1)}eisg;
	}
	for(keys %todo) {
		$$bref =~ s,\[$region\](.*?)\[/$region\],,igs;
	}
	return $$bref;
}

sub {
	my ($table, $key, $opt, $template) = @_;
show_times("begin table editor call item_id=$key") if $Global::ShowTimes;

	package Vend::Interpolate;
	use vars qw/$Values $Scratch $Db $Tag $Config $CGI $Variable $safe_safe/;

	my %hidden;

	if(ref($opt->{all_opts}) eq 'HASH') {
		my $o = $opt->{all_opts};
		for (keys %$o ) {
			$opt->{$_} = $o->{$_};
		}
	}
#Debug("options now=" . ::uneval($opt));

	init_calc() if ! $Vend::Calc_initialized;

	my @messages;
	my @errors;
	my %opt_out;

#Debug("labels=" . uneval($opt->{label}));
	FORMATS: {
		no strict 'refs';
		my $ref;
		for(qw/
					default     
					error       
					extra       
					filter      
					height      
					help        
					label       
					override    
					passed      
					options      
					outboard
					append
					prepend
					lookup
					lookup_query
					field
					pre_filter  
					widget      
					width       
					meta       
				/ )
		{
			next if ref $opt->{$_};
			($opt->{$_} = {}, next) if ! $opt->{$_};
			my $ref = {};
			my $string = $opt->{$_};
			$string =~ s/^\s+//gm;
			$string =~ s/\s+$//gm;
			while($string =~ m/^(.+?)=\s*(.+)/mg) {
				$ref->{$1} = $2;
			}
			$opt->{$_} = $ref;
		}
	}

	my $rowcount = 0;

	$opt->{action} ||= 'set';
	$opt_out{action} = $opt->{action};

	my $wizard_next   = $opt->{wizard_next}   || 'return';
	my $wizard_cancel = $opt->{wizard_cancel} || 'back';

	## Calculate the spans for across option
	my $rowdiv = $opt->{across} || 1;
	my $span = $rowdiv * 2;
	my $oddspan = $span - 1;
	$opt_out{calc_span} = $span;
	$opt_out{calc_oddspan} = $oddspan;

	my $def = $opt->{default_ref} || $::Values;
#::logDebug("view=$opt->{ui_meta_view}, default_ref: " . uneval($def));
	$opt->{table_width} = '60%' if ! $opt->{table_width};
	$opt->{left_width} = '30%' if ! $opt->{left_width};
	if (! $opt->{inner_table_width}) {
		if($opt->{table_width} =~ /%/) {
			$opt->{inner_table_width} = '100%';
		}
		elsif ($opt->{table_width} =~ /^\d+$/) {
			$opt->{inner_table_width} = $opt->{table_width} - 2;
		}
		else {
			$opt->{inner_table_width} = $opt->{table_width};
		}
	}
	my $check       = $opt->{check};
	my $default     = $opt->{default};
	my $error       = $opt->{error};
	my $extra       = $opt->{extra};
	my $filter      = $opt->{filter};
	my $height      = $opt->{height};
	my $help        = $opt->{help};
	my $help_url    = $opt->{help_url};
	my $label       = $opt->{label};
	my $override    = $opt->{override};
	my $pre_filter  = $opt->{pre_filter};
	my $passed      = $opt->{passed};
	my $options     = $opt->{options};
	my $outboard    = $opt->{outboard};
	my $prepend     = $opt->{prepend};
	my $append      = $opt->{append};
	my $lookup      = $opt->{lookup};
	my $lookup_query = $opt->{lookup_query};
	my $database    = $opt->{database};
	my $field       = $opt->{field};
	my $widget      = $opt->{widget};
	my $width       = $opt->{width};
	my $pmeta       = $opt->{meta};
#::logDebug("widget=" . ::uneval_it($widget) );
#::logDebug("label=" . ::uneval_it($label) );

	my $blabel      = $opt->{label_prepend} || '<span style="font-weight: normal">';
	my $elabel      = $opt->{label_append} || '</span>';
	my $mlabel = '';

	if($opt->{wizard}) {
		$opt->{noexport} = 1;
		$opt->{next_text} = 'Next -->' unless $opt->{next_text};
		$opt->{cancel_text} = 'Cancel' unless $opt->{cancel_text};
		$opt->{back_text} = '<-- Back' unless $opt->{back_text};
	}
	else {
		$opt->{cancel_text} = 'Cancel' unless $opt->{cancel_text};
		$opt->{next_text} = "Ok" unless $opt->{next_text};
	}

	for(qw/ next_text cancel_text back_text/ ) {
		$opt->{$_} = errmsg($opt->{$_});
	}

	my $ntext;
	my $btext;
	my $ctext;
	unless ($opt->{wizard} || $opt->{nosave}) {
		$Scratch->{$opt->{next_text}} = $Tag->return_to('click', 1);
	}
	else {
		if($opt->{action_click}) {
			$ntext = <<EOF;
mv_todo=$wizard_next
ui_wizard_action=Next
mv_click=$opt->{action_click}
EOF
		}
		else {
			$ntext = <<EOF;
mv_todo=$wizard_next
ui_wizard_action=Next
mv_click=ui_override_next
EOF
		}
		$Scratch->{$opt->{next_text}} = $ntext;

		my $hidgo = $opt->{mv_cancelpage} || $opt->{hidden}{ui_return_to} || $CGI->{return_to};
		$hidgo =~ s/\0.*//s;
		$ctext = $Scratch->{$opt->{cancel_text}} = <<EOF;
mv_form_profile=
ui_wizard_action=Cancel
mv_nextpage=$hidgo
mv_todo=$wizard_cancel
EOF
		if($opt->{mv_prevpage}) {
			$btext = $Scratch->{$opt->{back_text}} = <<EOF;
mv_form_profile=
ui_wizard_action=Back
mv_nextpage=$opt->{mv_prevpage}
mv_todo=$wizard_next
EOF
		}
		else {
			delete $opt->{back_text};
		}
	}

	for(qw/next_text back_text cancel_text/) {
		$opt->{"orig_$_"} = $opt->{$_};
	}

	$Scratch->{$opt->{next_text}}   = $ntext if $ntext;
	$Scratch->{$opt->{cancel_text}} = $ctext if $ctext;
	$Scratch->{$opt->{back_text}}   = $btext if $btext;

	$opt->{next_text} = HTML::Entities::encode($opt->{next_text}, $ESCAPE_CHARS::std);
	$opt->{back_text} = HTML::Entities::encode($opt->{back_text}, $ESCAPE_CHARS::std);
	$opt->{cancel_text} = HTML::Entities::encode($opt->{cancel_text});

	$Scratch->{$opt->{next_text}}   = $ntext if $ntext;
	$Scratch->{$opt->{cancel_text}} = $ctext if $ctext;
	$Scratch->{$opt->{back_text}}   = $btext if $btext;

	if($opt->{wizard} || $opt->{notable} and ! $table) {
		$table = 'mv_null';
		$Vend::Database{mv_null} = 
			bless [
					{},
					undef,
					[ 'code', 'value' ],
					[ 'code' => 0, 'value' => 1 ],
					0,
					{ },
					], 'Vend::Table::InMemory';
	}

	my @mapdirect = qw/
		mv_data_decode
		mv_data_table
		mv_blob_field
		mv_blob_nick
		mv_blob_pointer
		mv_blob_label
		mv_blob_title
		left_width
		table_width
		ui_break_before
		ui_break_before_label
		ui_data_fields
		ui_data_fields_all
		ui_data_key_name
		ui_display_only
		ui_hide_key
		ui_meta_specific
		ui_meta_view
		ui_nextpage
		ui_new_item
		ui_delete_box
		mv_update_empty
		defaults
		cgi_defaults
	/;

	$table = $CGI->{mv_data_table} if  $CGI->{mv_data_table} and ! $table;

	my $tmeta = UI::Primitive::meta_record($table, $opt->{ui_meta_view}) || {};

	for(grep defined $tmeta->{$_}, @mapdirect) {
		$opt->{$_} ||= $tmeta->{$_};
	}

	if($opt->{cgi}) {
		unshift @mapdirect, qw/
				item_id
				item_id_left
				ui_clone_id
				ui_clone_tables
				ui_sequence_edit
		/;
		for(@mapdirect) {
			next if ! defined $CGI->{$_};
			$opt->{$_} = $CGI->{$_};
		}
		my @hmap = (
			[ qr/^ui_te_check:/, $check ],
			[ qr/^ui_te_default:/, $default ],
			[ qr/^ui_te_extra:/, $extra ],
			[ qr/^ui_te_widget:/, $widget ],
			[ qr/^ui_te_passed:/, $passed ],
			[ qr/^ui_te_options:/, $options ],
			[ qr/^ui_te_outboard:/, $outboard ],
			[ qr/^ui_te_prepend:/, $prepend ],
			[ qr/^ui_te_append:/, $append ],
			[ qr/^ui_te_lookup:/, $lookup ],
			[ qr/^ui_te_database:/, $database ],
			[ qr/^ui_te_field:/, $field ],
			[ qr/^ui_te_override:/, $override ],
			[ qr/^ui_te_filter:/, $filter ],
			[ qr/^ui_te_pre_filter:/, $pre_filter ],
			[ qr/^ui_te_height:/, $height ],
			[ qr/^ui_te_width:/, $width ],
			[ qr/^ui_te_help:/, $help ],
			[ qr/^ui_te_help_url:/, $help_url ],
		);
		my @cgi = keys %{$CGI};
		foreach my $row (@hmap) {
			my @keys = grep $_ =~ $row->[0], @cgi;
			for(@keys) {
#::logDebug("found key $_");
				/^ui_\w+:(\S+)/
					and $row->[1]->{$1} = $CGI->{$_};
#::logDebug("set $1=$_");
			}
		}
		$table = $opt->{mv_data_table};
		$key = $opt->{item_id};
	}

	$hidden{mv_data_table} = $table;

	$opt->{color_success} = $Variable->{UI_C_SUCCESS} || '#00FF00'
		if ! $opt->{color_success};
	$opt->{color_fail} = $Variable->{UI_CONTRAST} || '#FF0000'
		if ! $opt->{color_fail};
	### Build the error checking
	my $error_show_var = 1;
	my $have_errors;
	if($opt->{ui_profile} or $check) {
		$Tag->error( { all => 1 } )
			unless $CGI->{mv_form_profile} or $opt->{keep_errors};
		my $prof = $opt->{ui_profile} || '';
		if ($prof =~ s/^\*//) {
			# special notation ui_profile="*whatever" means
			# use automatic checklist-related profile
			my $name = $prof;
			$prof = $Scratch->{"profile_$name"} || '';
			if ($prof) {
				$prof =~ s/^\s*(\w+)[\s=]+required\b/$1=mandatory/mg;
				for (grep /\S/, split /\n/, $prof) {
					if (/^\s*(\w+)\s*=(.+)$/) {
						my $k = $1; my $v = $2;
						$v =~ s/\s+$//;
						$v =~ s/^\s+//;
						$error->{$k} = 1;
						$error_show_var = 0 if $v =~ /\S /;
					}
				}
				$prof = '&calc delete \\$Values->{step_' . $name . "}\n" . $prof;
				$opt->{ui_profile_success} = "&set=step_$name 1";
			}
		}
		my $success = $opt->{ui_profile_success};
		if(ref $check) {
			while ( my($k, $v) = each %$check ) {
				$error->{$k} = 1;
				$v =~ s/\s+$//;
				$v =~ s/^\s+//;
				$v =~ s/\s+$//mg;
				$v =~ s/^\s+//mg;
				$v =~ s/^required\b/mandatory/mg;
				unless ($v =~ /^\&/m) {
					$error_show_var = 0 if $v =~ /\S /;
					$v =~ s/^/$k=/mg;
					$v =~ s/\n/\n&and\n/g;
				}
				$prof .= "$v\n";
			}
		}
		elsif ($check) {
			for (@_ = grep /\S/, split /[\s,]+/, $check) {
				$error->{$_} = 1;
				$prof .= "$_=mandatory\n";
			}
		}
		$opt->{hidden} = {} if ! $opt->{hidden};
		$opt->{hidden}{mv_form_profile} = 'ui_profile';
		my $fail = $opt->{mv_failpage} || $Global::Variable->{MV_PAGE};
		$Scratch->{ui_profile} = <<EOF;
[perl]
#Debug("cancel='$opt->{orig_cancel_text}' back='$opt->{orig_back_text}' click=\$CGI->{mv_click}");
	my \@clicks = split /\\0/, \$CGI->{mv_click};
	
	my \$fail = '$fail';
	for( qq{$opt->{orig_cancel_text}}, qq{$opt->{orig_back_text}}) {
#Debug("compare is '\$_'");
		next unless \$_;
		my \$cancel = \$_;
		for(\@clicks) {
#Debug("click is '\$_'");
			return if \$_ eq \$cancel; 
		}
	}
	
	return <<EOP;
$prof
&fail=$fail
&fatal=1
$success
mv_form_profile=mandatory
&set=mv_todo $opt->{action}
EOP
[/perl]
EOF
		$mlabel = ($opt->{message_label} || '&nbsp;&nbsp;&nbsp;<B>Bold</B> fields are required');
		$have_errors = $Tag->error( {
									all => 1,
									show_var => $error_show_var,
									show_error => 1,
									joiner => '<BR>',
									keep => 1}
									);
		if($opt->{all_errors}) {
			if($have_errors) {
				$mlabel .= '<P>Errors:';
				$mlabel .= qq{<FONT COLOR="$opt->{color_fail}">};
				$mlabel .= "<BLOCKQUOTE>$have_errors</BLOCKQUOTE></FONT>";
			}
		}
	}
	### end build of error checking

	$opt->{clear_image} = "bg.gif" if ! $opt->{clear_image};

#::logDebug("table-editor opt: " . ::uneval($opt));
	my $die = sub {
		::logError(@_);
		$Scratch->{ui_error} .= "<BR>\n" if $Scratch->{ui_error};
		$Scratch->{ui_error} .= ::errmsg(@_);
		return undef;
	};

	my $db;
	unless($opt->{notable}) {
		$db = Vend::Data::database_exists_ref($table)
			or return $die->('table-editor: bad table %s', $table);
	}

	if($opt->{ui_wizard_fields}) {
		$opt->{ui_data_fields} = $opt->{ui_display_only} = $opt->{ui_wizard_fields};
	}

	my $keycol;
	if($opt->{notable}) {
		$keycol = $opt->{ui_data_key_name};
	}
	else {
		$keycol = $opt->{ui_data_key_name} || $db->config('KEY');
	}

	$hidden{mv_data_key} = $keycol;

	if ($opt->{form_extra}) {
		$opt_out{form_extra} = $opt->{form_extra};
		$opt_out{form_extra} =~ s/^\s*/ /;
	}

	$opt_out{form_name} = qq{ NAME="$opt->{form_name}"}
		if $opt->{form_name};

	###############################################################
	# Get the field display information including breaks and labels
	###############################################################
	if( ! $opt->{ui_data_fields} and ! $opt->{ui_data_fields_all}) {
		$opt->{ui_data_fields} = $tmeta->{ui_data_fields} || $tmeta->{options};
	}

	$opt->{ui_data_fields} =~ s/\r\n/\n/g;
	$opt->{ui_data_fields} =~ s/\r/\n/g;
#::logDebug("ui_data_fields=$opt->{ui_data_fields}");

	if($opt->{ui_data_fields} =~ /\n\n/) {
#::logDebug("Found break fields");
		my @breaks;
		my @break_labels;
		while ($opt->{ui_data_fields} =~ s/\n+(?:\n[ \t]*=(.*))?\n+[ \t]*(\w[:.\w]+)/\n$2/) {
			push @breaks, $2;
			push @break_labels, "$2=$1" if $1;
		}
		$opt->{ui_break_before} = join(" ", @breaks)
			if ! $opt->{ui_break_before};
#::logDebug("break_before=$opt->{ui_break_before}");
		$opt->{ui_break_before_label} = join(",", @break_labels)
			if ! $opt->{ui_break_before_label};
#::logDebug("break_before_label=$opt->{ui_break_before_label}");
	}

	$opt->{ui_data_fields} ||= $opt->{mv_data_fields};

	if(! $opt->{ui_data_fields}) {
		if( $opt->{notable}) {
			::logError("table_editor: no place to get fields!");
			return '';
		}
		else {
			$opt->{ui_data_fields} = join " ", $db->columns();
		}
	}

	$opt->{ui_data_fields} =~ s/[,\0\s]+/ /g;
	###############################################################

	my $linecount;

	CANONCOLS: {
		my @cols = split /[,\0\s]/, $opt->{ui_data_fields};
		#@cols = grep /:/ || $db->column_exists($_), @cols;

		$opt->{ui_data_fields} = join " ", @cols;

		$linecount = scalar @cols;
	}

	my $url = $Tag->area('ui');

	my $key_message;
	if($opt->{ui_new_item} and ! $opt->{notable}) {
		if( ! $db->config('_Auto_number') ) {
			$db->config('AUTO_NUMBER', '000001');
			$key = $db->autonumber($key);
		}
		else {
			$key = '';
			$opt->{mv_data_auto_number} = 1;
			$key_message = '(new key will be assigned if left blank)';
		}
	}

	my $data;
	my $exists;

	if($opt->{notable}) {
		$data = {};
	}
	elsif($opt->{ui_clone_id} and $db->record_exists($opt->{ui_clone_id})) {
		$data = $db->row_hash($opt->{ui_clone_id})
			or
			return $die->('table-editor: row_hash function failed for %s.', $key);
		$data->{$keycol} = $key;
	}
	elsif ($db->record_exists($key)) {
		$data = $db->row_hash($key);
		$exists = 1;
	}

	if ($opt->{reload} and $have_errors) {
		if($data) {
			for(keys %$data) {
				$data->{$_} = $CGI->{$_}
					if defined $CGI->{$_};
			}
		}
		else {
			$data = { %$CGI };
		}
	}


	my $blob_data;
	my $blob_widget;
	if($opt->{mailto} and $opt->{mv_blob_field}) {
		$opt->{hidden}{mv_blob_only} = 1;
		$opt->{hidden}{mv_blob_nick}
			= $opt->{mv_blob_nick}
			|| POSIX::strftime("%Y%m%d%H%M%S", localtime());
	}
	elsif($opt->{mv_blob_field}) {
#::logDebug("checking blob");

		my $blob_pointer;
		$blob_pointer = $data->{$opt->{mv_blob_pointer}}
			if $opt->{mv_blob_pointer};
		$blob_pointer ||= $opt->{mv_blob_nick};
			

		DOBLOB: {

			unless ( $db->column_exists($opt->{mv_blob_field}) ) {
				push @errors, ::errmsg(
									"blob field %s not in database.",
									$opt->{mv_blob_field},
								);
				last DOBLOB;
			}

			my $bstring = $data->{$opt->{mv_blob_field}};

#::logDebug("blob: bstring=$bstring");

			my $blob;

			if(length $bstring) {
				$blob = $safe_safe->reval($bstring);
				if([email protected]) {
					push @errors, ::errmsg("error reading blob data: %s", [email protected]);
					last DOBLOB;
				}
#::logDebug("blob evals to " . ::uneval_it($blob));

				if(ref($blob) !~ /HASH/) {
					push @errors, ::errmsg("blob data not a storage book.");
					undef $blob;
				}
			}
			else {
				$blob = {};
			}
			my %wid_data;
			my %url_data;
			my @labels = keys %$blob;
			for my $key (@labels) {
				my $ref = $blob->{$_};
				my $lab = $ref->{$opt->{mv_blob_label} || 'name'};
				if($lab) {
					$lab =~ s/,/&#44/g;
					$wid_data{$lab} = "$key=$key - $lab";
					$url_data{$lab} = $Tag->page( {
											href => $Global::Variable->{MV_PAGE},
											form => "
												item_id=$opt->{item_id}
												mv_blob_nick=$key
											",
										});
					$url_data{$lab} .= "$key - $lab</A>";
				}
				else {
					$wid_data{$key} = $key;
					$url_data{$key} = $Tag->page( {
											href => $Global::Variable->{MV_PAGE},
											form => "
												item_id=$opt->{item_id}
												mv_blob_nick=$key
											",
										});
					$url_data{$key} .= "$key</A>";
				}
			}
#::logDebug("wid_data is " . ::uneval_it(\%wid_data));
			$opt->{mv_blob_title} = "Stored settings"
				if ! $opt->{mv_blob_title};
			$opt->{mv_blob_title} = errmsg($opt->{mv_blob_title});

			$Scratch->{Load} = <<EOF;
[return-to type=click stack=1 page="$Global::Variable->{MV_PAGE}"]
ui_nextpage=
[perl]Log("tried to go to $Global::Variable->{MV_PAGE}"); return[/perl]
mv_todo=back
EOF
#::logDebug("blob_pointer=$blob_pointer blob_nick=$opt->{mv_blob_nick}");

			my $loaded_from;
			my $lfrom_msg;
			if( $opt->{mv_blob_nick} ) {
				$lfrom_msg = $opt->{mv_blob_nick};
			}
			else {
				$lfrom_msg = errmsg("current values");
			}
			$lfrom_msg = errmsg("loaded from %s", $lfrom_msg);
			$loaded_from = <<EOF;
<I>($lfrom_msg)</I><BR>
EOF
			if(@labels) {
				$loaded_from .= errmsg("Load from") . ":<BLOCKQUOTE>";
				$loaded_from .=  join (" ", @url_data{ sort keys %url_data });
				$loaded_from .= "</BLOCKQUOTE>";
			}

			my $checked;
			my $set;
			if( $opt->{mv_blob_only} and $opt->{mv_blob_nick}) {
				$checked = ' CHECKED';
				$set 	 = $opt->{mv_blob_nick};
			}

			unless ($opt->{nosave}) {
				$blob_widget = $Tag->widget({
									name => 'mv_blob_nick',
									type => $opt->{ui_blob_widget} || 'combo',
									filter => 'nullselect',
									override => 1,
									set => "$set",
									passed => join (",", @wid_data{ sort keys %wid_data }) || 'default',
									});
				my $msg1 = errmsg('Save to');
				my $msg2 = errmsg('Save here only');
				for (\$msg1, \$msg2) {
					$$_ =~ s/ /&nbsp;/g;
				}
				$blob_widget = <<EOF unless $opt->{ui_blob_hidden};
<B>$msg1:</B> $blob_widget&nbsp;
<INPUT TYPE=checkbox NAME=mv_blob_only VALUE=1$checked>&nbsp;$msg2</SMALL>
EOF
			}

			$blob_widget = <<EOF unless $opt->{ui_blob_hidden};
<TR class=rnorm>
	 <td class=clabel width="$opt->{left_width}">
	   <SMALL>$opt->{mv_blob_title}<BR>
		$loaded_from
	 </td>
	 <td class=cwidget>
	 	$blob_widget&nbsp;
	 </td>
</TR>

<tr class=rtitle>
<td colspan=$span><img src="$opt->{clear_image}" width=1 height=3 alt=x></td>
</tr>
EOF

		if($opt->{mv_blob_nick}) {
			my @keys = split /::/, $opt->{mv_blob_nick};
			my $ref = $blob->{shift @keys};
			for(@keys) {
				my $prior = $ref;
				undef $ref;
				eval {
					$ref = $prior->{$_};
				};
				last DOBLOB unless ref $ref;
			}
			for(keys %$ref) {
				$data->{$_} = $ref->{$_};
			}
		}

		}
	}

#::logDebug("data is: " . ::uneval($data));
	$data = { $keycol => $key }
		if ! $data;

	if(! $opt->{mv_data_function}) {
		$opt->{mv_data_function} = $exists ? 'update' : 'insert';
	}

	$opt->{mv_nextpage} = $Global::Variable->{MV_PAGE} if ! $opt->{mv_nextpage};
	$opt->{mv_update_empty} = 1 unless defined $opt->{mv_update_empty};

	my $url_base = $opt->{secure} ? $Config->{SecureURL} : $Config->{VendURL};
#Debug("Urlbase=$url_base");
	$opt->{href} = "$url_base/ui" if ! $opt->{href};
	$opt->{href} = "$url_base/$opt->{href}"
		if $opt->{href} !~ m{^(https?:|)/};
#Debug("href=$opt->{href}");

	my $sidstr;
	if ($opt->{get}) {
		$opt->{method} = 'GET';
		$sidstr = '';
	}
	else {
		$opt->{method} = 'POST';
		$sidstr = qq{<INPUT TYPE=hidden NAME=mv_session_id VALUE="$Vend::Session->{id}">
};
	}

	$opt_out{href} = $opt->{href};
	$opt_out{method} = $opt->{method};
	$opt_out{tpl_sidstr} = $sidstr;
	$opt_out{enctype} = $opt->{file_upload} ? ' ENCTYPE="multipart/form-data"' : '';

	my %out_opt;

	my @opt_set = (qw/
						ui_meta_specific
						ui_hide_key
						ui_meta_view
						ui_data_decode
						mv_blob_field
						mv_blob_label
						mv_blob_title
						mv_blob_pointer
						mv_update_empty
						mv_nextpage
						mv_data_auto_number
						mv_data_function
				/ );

	my @cgi_set = ( qw/
						item_id_left
						ui_sequence_edit
					/ );

	push(@opt_set, splice(@cgi_set, 0)) if $opt->{cgi};
	for(@opt_set) {
		next unless length $opt->{$_};
		$hidden{$_} = $opt->{$_};
	}

	for (@cgi_set) {
		next unless length $CGI->{$_};
		$hidden{$_} = $CGI->{$_};
	}

	if($opt->{mailto}) {
		$opt->{mailto} =~ s/\s+/ /g;
		$Scratch->{mv_email_enable} = $opt->{mailto};
		$hidden{mv_data_email} = 1;
	}

	$Vend::Session->{ui_return_stack} ||= [];

	if($opt->{cgi}) {
		my $r_ary = $Vend::Session->{ui_return_stack};

#::logDebug("ready to maybe push/pop return-to from stack, stack = " . ::uneval($r_ary));
		if($CGI::values{ui_return_stack}++) {
			push @$r_ary, $CGI::values{ui_return_to};
			$CGI::values{ui_return_to} = $r_ary->[0];
		}
		elsif ($CGI::values{ui_return_to}) {
			@$r_ary = ( $CGI::values{ui_return_to} ); 
		}
		$opt_out{tpl_return_to} = $Tag->return_to();
#::logDebug("return-to stack = " . ::uneval($r_ary));
	}

	if(ref $opt->{hidden}) {
		my ($hk, $hv);
		while ( ($hk, $hv) = each %{$opt->{hidden}} ) {
			$hidden{$hk} = $hv;
		}
	}

	#### Extra buttons
	my $extra_ok =	$blob_widget
					|| $linecount > 4
					|| defined $opt->{include_form}
					|| $mlabel;
	if ($extra_ok and ! $opt->{no_top} and ! $opt->{nosave}) {
		my @out;
	  	if($opt->{back_text}) {
		  push @out, <<EOF;
<TR class=rnorm>
<td>&nbsp;</td>
<td align=left colspan=$oddspan class=cdata>
EOF
			push @out, <<EOF if ! $opt->{bottom_buttons};
<INPUT TYPE=submit NAME=mv_click VALUE="$opt->{back_text}">&nbsp;<INPUT TYPE=submit NAME=mv_click VALUE="$opt->{cancel_text}">&nbsp;<B><INPUT TYPE=submit NAME=mv_click VALUE="$opt->{next_text}"></B>
<BR>
EOF
			push @out, <<EOF;
$mlabel
</TD>
</TR>

<tr class=rspacer>
<td colspan=$span><img src="$opt->{clear_image}" width=1 height=3 alt=x></td>
</tr>
EOF
		}
		elsif ($opt->{wizard}) {
		  push @out, <<EOF;
<TR class=rnorm>
<td>&nbsp;</td>
<td align=left colspan=$oddspan class=cdata>
EOF
			push @out, <<EOF if ! $opt->{bottom_buttons};
<INPUT TYPE=submit NAME=mv_click VALUE="$opt->{cancel_text}">&nbsp;<B><INPUT TYPE=submit NAME=mv_click VALUE="$opt->{next_text}"></B>
<BR>
EOF
			push @out, <<EOF;
$mlabel
</TD>
</TR>

<tr class=rspacer>
<td colspan=$span><img src="$opt->{clear_image}" width=1 height=3 alt=x></td>
</tr>
EOF
		}
		else {
		  push @out, <<EOF;
<TR class=rnorm>
<td>&nbsp;</td>
<td align=left colspan=$oddspan class=cdata>
<B><INPUT TYPE=submit NAME=mv_click VALUE="$opt->{next_text}">
</B>
EOF
			push @out, <<EOF unless $opt->{nocancel};
&nbsp;
<INPUT TYPE=submit NAME=mv_click VALUE="$opt->{cancel_text}">$mlabel
EOF

			push @out, <<EOF if $opt->{show_reset};
&nbsp;
<INPUT TYPE=reset>
EOF

			push @out, <<EOF;
$mlabel
</TD>
</TR>

<tr class=rspacer>
<td colspan=$span><img src="$opt->{clear_image}" width=1 height=3 alt=x></td>
</tr>
EOF
		}
		$opt_out{tpl_top_buttons} = join "", @out;
	}

	$opt_out{tpl_blob_widget} = $blob_widget;

	  #### Extra buttons

	if($opt->{ui_new_item} and $opt->{ui_clone_tables}) {
		my @sets;
		my %seen;
		my @tables = split /[\s\0,]+/, $opt->{ui_clone_tables};
		for(@tables) {
			if(/:/) {
				push @sets, $_;
			}
			s/:.*//;
		}

		my %tab_checked;
		for(@tables, @sets) {
			$tab_checked{$_} = 1 if s/\*$//;
		}

		@tables = grep ! $seen{$_}++ && defined $Config->{Database}{$_}, @tables;

		my $tab = '';
		my $set .= <<'EOF';
[flag type=write table="_TABLES_"]
[perl tables="_TABLES_"]
	delete $Scratch->{clone_tables};
	return if ! $CGI->{ui_clone_id};
	return if ! $CGI->{ui_clone_tables};
	my $id = $CGI->{ui_clone_id};

	my $out = "Cloning id=$id...";

	my $new =  $CGI->{$CGI->{mv_data_key}}
		or do {
				$out .= ("clone $id: no mv_data_key '$CGI->{mv_data_key}'");
				$Scratch->{ui_message} = $out;
				return;
		};

	if($new =~ /\0/) {
		$new =~ s/\0/,/g;
		Log("cannot clone multiple keys '$new'.");
		return;
	}

	my %possible;
	my @possible = qw/_TABLES_/;
	@possible{@possible} = @possible;
	my @tables = grep /\S/, split /[\s,\0]+/, $CGI->{ui_clone_tables};
	my @sets = grep /:/, @tables;
	@tables = grep $_ !~ /:/, @tables;
	for(@tables) {
		next unless $possible{$_};
		my $db = $Db{$_};
		next unless $db;
		my $new = 
		my $res = $db->clone_row($id, $new);
		if($res) {
			$out .= "cloned $id to to $new in table $_<BR>\n";
		}
		else {
			$out .= "FAILED clone of $id to to $new in table $_<BR>\n";
		}
	}
	for(@sets) {
		my ($t, $col) = split /:/, $_;
		my $db = $Db{$t} or next;
		my $res = $db->clone_set($col, $id, $new);
		if($res) {
			$out .= "cloned $col=$id to to $col=$new in table $t<BR>\n";
		}
		else {
			$out .= "FAILED clone of $col=$id to to $col=$new in table $t<BR>\n";
		}
	}
	$Scratch->{ui_message} = $out;
	return;
[/perl]
EOF
		my $tabform = '';
		@tables = grep $Tag->if_mm( { table => "$_=i" } ), @tables;

		for(@tables) {
			my $db = Vend::Data::database_exists_ref($_)
				or next;
			next unless $db->record_exists($opt->{ui_clone_id});
			my $checked = $tab_checked{$_} ? ' CHECKED' : '';
			$tabform .= <<EOF;
<INPUT TYPE=CHECKBOX NAME=ui_clone_tables VALUE="$_"$checked> clone to <b>$_</B><BR>
EOF
		}
		for(@sets) {
			my ($t, $col) = split /:/, $_;
			my $checked = $tab_checked{$_} ? ' CHECKED' : '';
			$tabform .= <<EOF;
<INPUT TYPE=CHECKBOX NAME=ui_clone_tables VALUE="$_"$checked> clone entries of <b>$t</B> matching on <B>$col</B><BR>
EOF
		}

		my $tabs = join " ", @tables;
		$set =~ s/_TABLES_/$tabs/g;
		$Scratch->{clone_tables} = $set;
		$opt_out{tpl_clone} = <<EOF;
<tr class=rtitle>
<td colspan=$span>
$tabform<INPUT TYPE=hidden NAME=mv_check VALUE="clone_tables">
<INPUT TYPE=hidden NAME=ui_clone_id VALUE="$opt->{ui_clone_id}">
</td>
</tr>
EOF
	}

	my %break;
	my %break_label;
	if($opt->{ui_break_before}) {
#::logDebug("Have a break_before");
		my @tmp = grep /\S/, split /[\s,\0]+/, $opt->{ui_break_before};
		@break{@tmp} = @tmp;
		if($opt->{ui_break_before_label}) {
			@tmp = grep /\S/, split /\s*[,\0]\s*/, $opt->{ui_break_before_label};
			for(@tmp) {
				my ($br, $lab) = split /\s*=\s*/, $_;
				$break_label{$br} = $lab;
			}
		}
	}
	if(!$db and ! $opt->{notable}) {
		return "<TR><TD>Broken table '$table'</TD></TR>";
	}

	my $passed_fields = $opt->{ui_data_fields};

#::logDebug("extra=" . ::uneval($extra));
	my @extra_cols;
	my %email_cols;
	my %ok_col;
	my @cols;
	my @dbcols;
	my %display_only;

	if($opt->{notable}) {
		@cols = split /[\s,\0]+/, $passed_fields;
	}
	else {

		while($passed_fields =~ s/(\w+[.:]+\S+)//) {
			push @extra_cols, $1;
		}

		my @do = grep /\S/, split /[\0,\s]+/, $opt->{ui_display_only};
		for(@do) {
			$email_cols{$_} = 1 if $opt->{mailto};
			$display_only{$_} = 1;
			push @extra_cols, $_;
		}

		@dbcols  = split /\s+/, $Tag->db_columns( {
											name	=> $table,
											columns	=> $passed_fields,
											passed_order => 1,
										});

		if($opt->{ui_data_fields}) {
			for(@dbcols, @extra_cols) {
				unless (/^(\w+)([.:]+)(\S+)/) {
					$ok_col{$_} = 1;
					next;
				}
				my $t = $1;
				my $s = $2;
				my $c = $3;
				if($s eq '.') {
					$c = $t;
					$t = $table;
				}
				else {
					$c =~ s/\..*//;
				}
				next unless $Tag->db_columns( { name	=> $t, columns	=> $c, });
				$ok_col{$_} = 1;
			}
		}
		@cols = grep $ok_col{$_}, split /\s+/, $opt->{ui_data_fields};
	}

	$keycol = $cols[0] if ! $keycol;

	if($opt->{defaults}) {
		if($opt->{cgi_defaults}) {
			$default->{$_} = $CGI->{$_} for @cols;
		}
		elsif($opt->{force_defaults}) {
			$default->{$_} = $def->{$_} for @cols;
		}
		elsif($opt->{wizard}) {
			for(@cols) {
				$default->{$_} = $def->{$_} if defined $def->{$_};
			}
		}
		else {
			for(@cols) {
				next if defined $default->{$_};
				next unless defined $def->{$_};
				$default->{$_} = $def->{$_};
			}
		}
	}

	my $super = $Tag->if_mm('super');

	my $refkey = $key;

	my @data_enable = ($opt->{mv_blob_pointer}, $opt->{mv_blob_field});
	my @ext_enable;

	my %serialize;
	my %serial_data;

	my @controls;

	if(my $jsc = $opt->{js_changed}) {
		$jsc =~ /^\w+$/
			and $jsc = qq{onChange="$jsc} . q{('$$KEY$$','$$COL$$');"};
		foreach my $c (@cols) {
			next if $extra->{$c} =~ /\bonchange\s*=/i;
			my $tpl = $jsc;
			$tpl .= $extra->{$c} if length $extra->{$c};
			$tpl =~ s/\$\$KEY\$\$/$key/g;
			$tpl =~ s/\$\$COL\$\$/$c/g;
			if ($extra->{$c} and $extra->{$c} =~ /\bonchange\s*=/i) {
				$tpl =~ s/onChange="//;
				$tpl =~ s/"\s*$/;/;
				$extra->{$c} =~ s/\b(onchange\s*=\s*["'])/$1$tpl/i;
			}
			else {
				$extra->{$c} = $tpl;
			}
		}
	}

	foreach my $col (@cols) {
		my $t;
		my $c;
		my $k;
		my $tkey_message;
		my $ref = {};

		if($col eq $keycol) {
			if($opt->{ui_hide_key}) {
				my $kval = $key || $override->{$col} || $default->{$col};
				$hidden{$col} = $kval;
				next;
			}
			elsif ($opt->{ui_new_item}) {
				$tkey_message = $key_message;
			}
		}

		my $do = $display_only{$col};
		
		my $currval;
		my $serialize;

		if($col =~ /(\w+):+([^:]+)(?::+(\S+))?/) {
			$t = $1;
			$c = $2;
			$c =~ /(.+?)\.\w.*/
				and $col = "$t:$1"
					and $serialize = $c;
			$k = $3 || undef;
			push @ext_enable, ("$t:$c" . $k ? ":$k" : '')
				unless $do;
		}
		else {
			$t = $table;
			$c = $col;
			$c =~ /(.+?)\.\w.*/
				and $col = $1
					and $serialize = $c;
			push @data_enable, $col
				unless $do and ! $opt->{mailto};
		}

		my $type;
		my $overridden;

		$currval = $data->{$col} if defined $data->{$col};
		if ($opt->{force_defaults} or defined $override->{$c} ) {
			$currval = $override->{$c};
			$overridden = 1;
#::logDebug("hit override for $col,currval=$currval");
		}
		elsif (defined $CGI->{"ui_preload:$t:$c"} ) {
			$currval = delete $CGI->{"ui_preload:$t:$c"};
			$overridden = 1;
#::logDebug("hit preload for $col,currval=$currval");
		}
		elsif( ($do && ! $currval) or $col =~ /:/) {
			if(defined $k) {
				my $check = $k;
				undef $k;
				for( $override, $data, $default) {
					next unless defined $_->{$check};
					$k = $_->{$check};
					last;
				}
			}
			else {
				$k = defined $key ? $key : $refkey;
			}
			$currval = tag_data($t, $c, $k) if defined $k;
#::logDebug("hit display_only for $col, t=$t, c=$c, k=$k, currval=$currval");
		}
		elsif (defined $default->{$c} and ! length($data->{$c}) ) {
			$currval = $default->{$c};
#::logDebug("hit preload for $col,currval=$currval");
		}
		else {
#::logDebug("hit data->col for $col, t=$t, c=$c, k=$k, currval=$currval");
			$currval = length($data->{$col}) ? $data->{$col} : '';
			$overridden = 1;
		}

		my $namecol;
		if($serialize) {
#Debug("serialize=$serialize");
			if($serialize{$col}) {
				push @{$serialize{$col}}, $serialize;
			}
			else {
				my $sd;
				if($col =~ /:/) {
					my ($tt, $tc) = split /:+/, $col;
					$sd = tag_data($tt, $tc, $k);
				}
				else {
					$sd = $data->{$col} || $def->{$col};
				}
#Debug("serial_data=$sd");
				$serial_data{$col} = $sd;
				$opt->{hidden}{$col} = $data->{$col};
				$serialize{$col} = [$serialize];
			}
			$c =~ /\.(.*)/;
			my $hk = $1;
#Debug("fetching serial_data for $col hk=$hk data=$serial_data{$col}");
			$currval = dotted_hash($serial_data{$col}, $hk);
#Debug("fetched hk=$hk value=$currval");
			$overridden = 1;
			$namecol = $c = $serialize;
		}

		$namecol = $col unless $namecol;

		$type = 'value' if $do and ! ($opt->{wizard} || ! $opt->{mailto});

		if (! length $currval and defined $default->{$c}) {
			$currval = $default->{$c};
		}

		my $meta = '';
		if($error->{$c}) {
			my $parm = {
					name => $c,
					std_label => '$LABEL$',
					required => 1,
					};
			if($opt->{all_errors}) {
				$parm->{keep} = 1;
				$parm->{text} = <<EOF;
<FONT COLOR="$opt->{color_fail}">\$LABEL\$</FONT><!--%s-->
[else]{REQUIRED <B>}{LABEL}{REQUIRED </B>}[/else]
EOF
			}
			$ref->{label} = $Tag->error($parm);
		}
#::logDebug("col=$c currval=$currval widget=$widget->{$c} label=$label->{$c} (type=$type)");
		my ($drw, $drl, $drh, $drhu) = $Tag->display({
										applylocale => 1,
										arbitrary => $opt->{ui_meta_view},
										column => $c,
										default => $currval,
										extra => $extra->{$c},
										fallback => 1,
										field => $field->{$c},
										filter => $filter->{$c},
										height => $height->{$c},
										help => $help->{$c},
										help_url => $help_url->{$c},
										label => $label->{$c},
										key => $key,
										meta => $pmeta->{$c},
										name => $namecol,
										override => $overridden,
										passed => $passed->{$c},
										options => $options->{$c},
										outboard => $outboard->{$c},
										append => $append->{$c},
										prepend => $prepend->{$c},
										lookup => $lookup->{$c},
										lookup_query => $lookup_query->{$c},
										db => $database->{$c},
										pre_filter => $pre_filter->{$c},
										table => $t,
										type => $widget->{$c} || $type,
										width => $width->{$c},
										template => $template,
									});
		$ref->{widget}   = $drw;
		$ref->{label}  ||= $drl; ## May have been set previously
		$ref->{help}	 = $drh;
		$ref->{help_url} = $drhu;

#::logDebug("finished display of col=$c");
		if($super and ! $opt->{no_meta} and ($Variable->{UI_META_LINK} || $def->{ui_meta_force}) ) {
			$meta .= '<BR><FONT SIZE=1>';
			# Get global variables
			my $base = $Tag->var('UI_BASE', 1);
			my $page = $Tag->var('MV_PAGE', 1);
			my $id = $t . "::$c";
			$id = $opt->{ui_meta_view} . "::$id"
				if $opt->{ui_meta_view} and $opt->{ui_meta_view} ne 'metaconfig';

			my $return = <<EOF;
ui_return_to=$page
ui_return_to=item_id=$opt->{item_id}
ui_return_to=ui_meta_view=$opt->{ui_meta_view}
ui_return_to=mv_return_table=$t
mv_return_table=$table
ui_return_stack=$CGI->{ui_return_stack}
EOF

			$meta .= $Tag->page(
							{	href => "$base/meta_editor",
								form => qq{
										item_id=$id
										$return
										}
							});
			$meta .= 'meta</A>';
			$meta .= '<br>' . $Tag->page(
							{	href => "$base/meta_editor",
								form => qq{
										item_id=${t}::${c}::$key
										$return
										}
							}) . 'item-specific meta</A></FONT>'
				if $opt->{ui_meta_specific};
			$meta .= '</FONT>';
			$ref->{meta} = $meta;
		}
		$ref->{error} = $Tag->error({ name => $c, keep => 1 });
        
		if ($break{$namecol}) {
			my $w = '';
			while($rowcount % $rowdiv) {
				$w .= '<TD>&nbsp;</td><TD>&nbsp;</td>';
				$rowcount++;
			}
			$w .= "</TR>\n";
			$w .= <<EOF if $break{$namecol};
<TR class=rbreak>
	<TD COLSPAN=$span class=cbreak>$break_label{$namecol}<IMG SRC="$opt->{clear_image}" WIDTH=1 HEIGHT=1 alt=x></TD>
</TR>
EOF
			$rowcount = 0;
			$ref->{break} = $w;
		}
		push @controls, $ref;
	}

	$Scratch->{mv_data_enable} = '';
	if($opt->{auto_secure}) {
		$Scratch->{mv_data_enable} .= "$table:" . join(",", @data_enable) . ':';
		$Scratch->{mv_data_enable_key} = $opt->{item_id};
	}
	if(@ext_enable) {
		$Scratch->{mv_data_enable} .= " " . join(" ", @ext_enable) . " ";
	}
#Debug("setting mv_data_enable to $Scratch->{mv_data_enable}");
	my @serial = keys %serialize;
	my @serial_fields;
	for (@serial) {
#Debug("$_ serial_data=$serial_data{$_}");
		$serial_data{$_} = uneval($serial_data{$_})
			if is_hash($serial_data{$_});
		$hidden{$_} = $serial_data{$_};
		push @serial_fields, @{$serialize{$_}};
	}

	if(@serial_fields) {
		$hidden{ui_serial_fields} = join " ", @serial_fields;
	}

	###
	### Here the user can include some extra stuff in the form....
	###
	$opt_out{tpl_include_form} = <<EOF if $opt->{include_form};
<tr class=rnorm>
<td colspan=$span>$opt->{include_form}</td>
</tr>
EOF
	### END USER INCLUDE

	unless ($opt->{mailto} and $opt->{mv_blob_only}) {
		@cols = grep ! $display_only{$_}, @cols;
	}
	$passed_fields = join " ", @cols;

	$hidden{mv_data_fields} = $passed_fields;

  SAVEWIDGETS: {
  	last SAVEWIDGETS if $opt->{nosave}; 
	my @out;
		if($opt->{back_text}) {
		  push @out, <<EOF;
<TR class=rnorm>
<td>&nbsp;</td>
<td align=left colspan=$oddspan class=cdata>
<INPUT TYPE=submit NAME=mv_click VALUE="$opt->{back_text}">&nbsp;<INPUT TYPE=submit NAME=mv_click VALUE="$opt->{cancel_text}">&nbsp;<B><INPUT TYPE=submit NAME=mv_click VALUE="$opt->{next_text}"></B>
EOF
		}
		elsif($opt->{wizard}) {
		  push @out, <<EOF;
<TR class=rnorm>
<td>&nbsp;</td>
<td align=left colspan=$oddspan class=cdata>
<INPUT TYPE=submit NAME=mv_click VALUE="$opt->{cancel_text}">&nbsp;<B><INPUT TYPE=submit NAME=mv_click VALUE="$opt->{next_text}"></B>
EOF
		}
		else {
			push @out, qq{<TR class=rnorm>
<td>&nbsp;</td>
<td align=left colspan=$oddspan class=cdata>
<B><INPUT TYPE=submit NAME=mv_click VALUE="$opt->{next_text}"></B>};
			push @out,
				qq{&nbsp;<INPUT TYPE=submit NAME=mv_click VALUE="$opt->{cancel_text}">}
				unless $opt->{nocancel};
			push @out, qq{&nbsp;<INPUT TYPE=reset>}
				if $opt->{show_reset};
		}

	if(! $opt->{notable} and $Tag->if_mm('tables', "$table=x") and ! $db->config('LARGE') ) {
		my $checked = ' CHECKED';
		$checked = ''
			if defined $opt->{mv_auto_export} and ! $opt->{mv_auto_export};
		my $autoexpstr = errmsg('Auto-export');		
		push @out, <<EOF unless $opt->{noexport} or $opt->{nosave};
<small>
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
	<INPUT TYPE=checkbox NAME=mv_auto_export VALUE="$table"$checked>&nbsp;$autoexpstr
EOF

	}

	if($exists and ! $opt->{nodelete} and $Tag->if_mm('tables', "$table=d")) {
		my $extra = $Tag->return_to( { type => 'click', tablehack => 1 });
		my $page = $CGI->{ui_return_to};
		$page =~ s/\0.*//s;
		my $url = $Tag->area( {
					href => $page,
					form => qq!
						deleterecords=1
						ui_delete_id=$key
						mv_data_table=$table
						mv_click=db_maintenance
						mv_action=back
						$extra
					!,
					});
		push @out, <<EOF if ! $opt->{nosave};
<BR><BR><A
onClick="return confirm('Are you sure you want to delete $key?')"
HREF="$url"><IMG SRC="delete.gif" ALT="Delete $key" BORDER=0></A> Delete
EOF
	}
	push @out, <<EOF;
</small>
</td>
</tr>
EOF

	$opt_out{tpl_bottom_buttons} = join "", @out;
  } # end SAVEWIDGETS

	my $message = '';

	if($opt->{bottom_errors}) {
		my $err = $Tag->error( {
									show_var => $error_show_var,
									show_error => 1,
									joiner => '<BR>',
								}
								);
		push @errors, $err if $err;
	}

	$opt->{color_fail} ||= 'red';
	$opt->{color_success} ||= 'green';
	if(@errors) {
#		$message .= '<P>Errors:';
#		$message .= qq{<FONT COLOR="$opt->{color_fail}">};
#		$message .= '<BLOCKQUOTE>';
#		$message .= join "<BR>", @errors;
#		$message .= '</BLOCKQUOTE></FONT>';
		my $join = $opt->{error_joiner} || '<BR>';
		$opt_out{tpl_errors} = join $join, @errors;
	}
	if(@messages) {
#		$message .= '<P>Messages:';
#		$message .= qq{<FONT COLOR="$opt->{color_success}">};
#		$message .= '<BLOCKQUOTE>';
#		$message .= join "<BR>", @messages;
#		$message .= '</BLOCKQUOTE></FONT>';
		my $join = $opt->{message_joiner} || '<BR>';
		$opt_out{tpl_messages} = join $join, @messages;
	}
	$Tag->error( { all => 1 } ) unless $opt->{no_error_clear};

	my $eregion = editor_region($opt, \%opt_out, \%hidden);
	my $cregion = column_region($opt, \%opt_out, \@controls);

	$template ||= $Default_template;
show_times("end table editor format item_id=$key") if $Global::ShowTimes;
	my $result = run_templates( $opt, [ @$eregion, @$cregion ], \$template);
show_times("end table editor region display item_id=$key") if $Global::ShowTimes;
	return $result;
	
}
EOR