Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
4702 lines (4205 sloc) 118 KB
# Vend::Table::Editor - Swiss-army-knife table editor for Interchange
#
# Copyright (C) 2002-2018 Interchange Development Group
# Copyright (C) 2002 Mike Heins <mike@perusion.net>
#
# This program was originally based on Vend 0.2 and 0.3
# Copyright 1995 by Andrew M. Wilcox <amw@wilcoxsolutions.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
# MA 02110-1301 USA.
package Vend::Table::Editor;
use vars qw($VERSION);
$VERSION = '1.94';
use Vend::Util;
use Vend::Interpolate;
use Vend::Data;
use Exporter;
@EXPORT_OK = qw/meta_record expand_values tabbed_display display/;
use strict;
no warnings qw(uninitialized numeric);
=head1 NAME
Vend::Table::Editor -- Interchange do-all HTML table editor
=head1 SYNOPSIS
[table-editor OPTIONS]
[table-editor OPTIONS] TEMPLATE [/table-editor]
=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.
=cut
my $Tag = new Vend::Tags;
my $Trailer;
use vars qw/%Display_type %Display_options %Style_sheet/;
%Display_options = (
three_column => sub {
my $opt = shift;
$opt->{cell_span} = 3;
return;
},
nospace => sub {
my $opt = shift;
$opt->{break_cell_first_style} ||= 'border-top: 1px solid #999999';
return;
},
adjust_width => sub {
my $opt = shift;
$opt->{adjust_cell_class} ||= $opt->{widget_cell_class};
if($opt->{table_width} and $opt->{table_width} =~ /^\s*(\d+)(\w*)\s*$/) {
my $wid = $1;
my $type = $2 || '';
$opt->{help_cell_style} ||= 'width: ' . int($wid * .35) . $type;
}
else {
$opt->{help_cell_style} ||= 'width: 400';
}
return;
},
);
%Display_type = (
default => sub {
my $opt = shift;
my $thing = <<EOF;
<td title="{COLUMN}"$opt->{label_cell_extra}>
{BLABEL}{LABEL}{ELABEL}{META_STRING}
</td>
<td$opt->{data_cell_extra}\{COLSPAN}>
<table cellspacing="0" cellmargin="0" width="100%">
<tr>
<td$opt->{widget_cell_extra}>
{WIDGET}
</td>
<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>
</tr>
</table>
</td>
EOF
chomp $thing;
return $thing;
},
blank => sub {
my $opt = shift;
my $thing = <<EOF;
EOF
chomp $thing;
return $thing;
},
nospace => sub {
my $opt = shift;
my $span = shift;
$opt->{break_template} ||= <<EOF;
<tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}\{FIRST?} style="$opt->{break_cell_first_style}"{/FIRST?}>{ROW}</td></tr>
EOF
my $thing = <<EOF;
<td title="{COLUMN}"$opt->{label_cell_extra}>
{BLABEL}{LABEL}{ELABEL}
</td>
<td$opt->{data_cell_extra}\{COLSPAN}>
<table cellspacing="0" cellmargin="0" width="100%">
<tr>
<td$opt->{widget_cell_extra}>
{WIDGET}
</td>
<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>
<td align="right">{META_STRING}</td>
</tr>
</table>
</td>
EOF
chomp $thing;
return $thing;
},
text_js_help => sub {
my $opt = shift;
my $thing = <<EOF;
<td title="{COLUMN}"$opt->{label_cell_extra}>
{BLABEL}{LABEL}{ELABEL}
</td>
<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?}
</td>
EOF
chomp $thing;
return $thing;
},
three_column => sub {
my $opt = shift;
my $thing = <<EOF;
<td title="{COLUMN}"$opt->{label_cell_extra}>
{BLABEL}{LABEL}{ELABEL}
</td>
<td$opt->{data_cell_extra}\{COLSPAN} nowrap>
{WIDGET}
</td>
<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?}
</td>
EOF
chomp $thing;
return $thing;
},
simple_row => sub {
#::logDebug("calling simple_row display");
my $opt = shift;
my $thing = <<EOF;
<td title="{COLUMN}"$opt->{label_cell_extra}>
{BLABEL}{LABEL}{ELABEL}
</td>
<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?}
</td>
EOF
chomp $thing;
return $thing;
},
over_under => sub {
my $opt = shift;
my $thing = <<EOF;
{HELP?}
<td colspan="2"$opt->{help_cell_extra}>
{HELP}
</td>
</tr>
<tr>
{/HELP?} <td title="{COLUMN}" colspan="2"$opt->{label_cell_extra}>
{LABEL}
</td>
</tr>
<tr>
<td colspan="2"$opt->{widget_cell_extra}>
{WIDGET}
</td>
EOF
chomp $thing;
return $thing;
},
adjust_width => sub {
my $opt = shift;
my $span = shift;
$opt->{break_template} ||= <<EOF;
$opt->{spacer_row}
<tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}>{ROW}</td></tr>
EOF
my $thing = <<EOF;
<td title="{COLUMN}"$opt->{label_cell_extra}>
{BLABEL}{LABEL}{ELABEL}
</td>
<td$opt->{data_cell_extra}\{COLSPAN}>
<table cellspacing="0" cellmargin="0" width="100%">
<tr>
<td$opt->{widget_cell_extra}>
{WIDGET}
</td>
<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>
<td align="right">{META_STRING}</td>
</tr>
</table>
</td>
EOF
chomp $thing;
return $thing;
},
image_meta => sub {
my $opt = shift;
my $span = shift;
$opt->{break_template} ||= <<EOF;
$opt->{spacer_row}
<tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}>{ROW}</td></tr>
EOF
my $thing = <<EOF;
<td title="{COLUMN}"$opt->{label_cell_extra}>
{BLABEL}{LABEL}{ELABEL}
</td>
<td$opt->{data_cell_extra}\{COLSPAN}>
<table cellspacing="0" cellmargin="0" width="100%">
<tr>
<td$opt->{widget_cell_extra}>
{WIDGET}
</td>
<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>
<td align="right">{META_STRING}</td>
</tr>
</table>
</td>
EOF
chomp $thing;
return $thing;
},
simple_help_below => sub {
my $opt = shift;
my $thing = <<EOF;
<td title="{COLUMN}"$opt->{label_cell_extra}>
{BLABEL}{LABEL}{ELABEL}
</td>
<td$opt->{data_cell_extra}\{COLSPAN}>
{WIDGET}
{HELP_EITHER?}<br$Trailer>{/HELP_EITHER?}
{HELP}{HELP_URL?}<br$Trailer><a href="{HELP_URL}">$opt->{help_anchor}</a>{/HELP_URL?}
{META_URL?}<a href="{META_URL}">$opt->{meta_anchor}</a>{/META_URL?}
</td>
</tr>
</table>
</td>
EOF
chomp $thing;
return $thing;
},
simple_icon_help => sub {
my $opt = shift;
$opt->{help_icon} ||= '/icons/small/unknown.gif';
my $thing = <<EOF;
<td title="{COLUMN}"$opt->{label_cell_extra}>
{BLABEL}{LABEL}{ELABEL}
</td>
<td$opt->{data_cell_extra}\{COLSPAN}>
<table width="100%">
<tr>
<td style="padding-left: 3px">
{WIDGET}
</td>
<td align="right">
{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?}
</td>
</tr>
</table>
</td>
EOF
chomp $thing;
return $thing;
},
);
my %dt_map = qw/
simple_row 1
text_help 2
simple_icon_help 3
over_under 4
simple_help_below 5
image_meta 6
three_column 7
nospace 8
/;
for(keys %dt_map) {
$Display_type{$dt_map{$_}} = $Display_type{$_}
if $Display_type{$_};
$Display_options{$dt_map{$_}} = $Display_options{$_}
if $Display_options{$_};
}
%Style_sheet = (
default => <<EOF,
<style type="text/css">
.rborder {
background-color: #CCCCCC;
margin: 0;;
padding: 2
}
.rhead {
background-color: #E6E6E6;;
color: #000000;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px
}
A.rhead:active,A.rhead:hover {
color: #000000;
font-size: 12px;
text-decoration: underline;
}
A.rhead:link,A.rhead:visited {
color: #000000;
font-size: 12px;
text-decoration:none;
}
.rheadBold {
background-color: #E6E6E6;
color: #000000;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
font-weight: bold;;
padding: 4px
}
.rheader {
background-color: #999999;
color: #663333;
}
.rmarq {
background-color: #999999;;
color: #FFFFFF;
font-size: 12px;
font-weight: bold
}
A.rmarq:active,A.rmarq:link,A.rmarq:visited {
color: #FFFFCC;
font-size: 12px;
font-weight: bold;
text-decoration:none;
}
A.rmarq:hover {
color: #FFFF99;
font-size: 12px;
font-weight: bold;
text-decoration: underline;
}
.rnobg {
color: #000000;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
padding: 4px;
}
.rnorm {
background-color: #FFFFFF;
border: 1px solid #CCCCCC;
}
.rowalt {
background-color: #EAF1FB;
color: #000000;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
padding: 4px;
}
A.rowalt:hover,A.rowalt:hover,A.rownorm:active,A.rownorm:hover {
color: #333333;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
text-decoration: underline;
}
A.rowalt:link,A.rowalt:visited,A.rownorm:link,A.rownorm:visited {
color: #333333;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
text-decoration:none;
}
.rownorm {
background-color: #FFFFFF;
color: #000000;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
padding: 4px;
}
.rownormbold {
background-color: #FFFFFF;
color: #000000;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
font-weight: bold;;
padding: 4px
}
.rseparator {
background-color: #CCCCCC;
}
.rshade {
background-color: #E6E6E6;
color: #000000;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
padding: 4px;
}
.rspacer {
background-color: #999999;
margin: 0;;
padding: 0
}
.rsubbold {
background-color: #FFFFFF;
color: #808080;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
font-weight: bold;;
padding: 4px
}
.rtitle {
background-color: #808080;;
color: #FFFFFF;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 12px;
font-weight: bold
}
A.rtitle:active,A.rtitle:link,A.rtitle:visited {
color: #FFFFCC;
font-size: 12px;
font-weight: bold;
text-decoration:none;
}
.s1,.s2 {
color: #666666;;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 10px
}
.s3 {
color: #333333;;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 10px
}
.s4 {
color: #666666;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 10px;
width: 100%;
}
.rbreak {
background-color: #FFFFFF;
}
.cborder {
background-color: #999999;
padding: 0;
}
.cbreak {
background-color: #EEEEEE;
border-left: 1px solid #999999;
font-size: 11px;;
font-weight: bold
}
.cdata {
border-bottom: 1px solid #CCCCCC;
border-right: 1px solid #CCCCCC;
border-top: 1px solid #CCCCCC;
font-size: 11px;;
margin-right: 4px;
padding-right: 2px;
vertical-align: top
}
.cerror {
color: red;
font-size: 11px;
}
.cheader {
color: #663333;
font-size: 11px;;
font-weight: bold
}
.chelp,.rhint {
color: #AFABA5;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
padding: 4px;
}
.clabel {
border-bottom: 1px solid #CCCCCC;
border-left: 1px solid #CCCCCC;
border-top: 1px solid #CCCCCC;
font-size: 11px;
vertical-align: top;
font-weight: medium;
padding-left: 5px;;
text-align: left
}
.cmiddle {
border-bottom: 1px solid #CCCCCC;
border-top: 1px solid #CCCCCC;
font-size: 11px;
font-weight: medium;
padding-left: 5px;;
text-align: middle
}
.cmessage {
color: green;
font-size: 11px;
}
A:link.ctitle,A:visited.ctitle {
color: white;
font-size: 11px;;
font-weight: bold;
text-decoration: none
}
A:hover.ctitle,A:active.ctitle {
color: yellow;
font-size: 11px;;
font-weight: bold;
text-decoration: underline
}
.ctitle {
font-size: 11px;;
font-weight: bold
}
.cwidget {
font-size: 11px;;
vertical-align: center
}
</style>
EOF
);
sub expand_values {
my $val = shift;
return $val unless $val =~ /\[/;
$val =~ s/\[cgi\s+([^\[]+)\]/$CGI::values{$1}/ig;
$val =~ s/\[var\s+([^\[]+)\]/$::Variable->{$1}/ig;
$val =~ s/\[value\s+([^\[]+)\]/$::Values->{$1}/ig;
return $val;
}
sub widget_meta {
my ($type,$opt) = @_;
my $meta = meta_record("_widget::$type", $opt->{view}, $opt->{meta_table}, 1);
return $meta if $meta;
my $w = $Vend::Cfg->{CodeDef}{Widget};
if($w and $w->{Widget}{$type}) {
my $string;
return undef unless $string = $w->{ExtraMeta}{$type};
return get_option_hash($string);
}
$w = $Global::CodeDef->{Widget};
if($w and $w->{Widget}{$type}) {
my $string;
return undef unless $string = $w->{ExtraMeta}{$type};
return get_option_hash($string);
}
return $Vend::Form::ExtraMeta{$type};
}
sub meta_record {
my ($item, $view, $mdb, $extended_only, $overlay) = @_;
#::logDebug("meta_record: item=$item view=$view mdb=$mdb");
return undef unless $item;
my $mtable;
if(! ref ($mdb)) {
$mtable = $mdb || $::Variable->{UI_META_TABLE} || 'mv_metadata';
#::logDebug("meta_record mtable=$mtable");
$mdb = database_exists_ref($mtable)
or return undef;
}
#::logDebug("meta_record has an item=$item and mdb=$mdb");
my $record;
my $mkey = $view ? "${view}::$item" : $item;
if( ref ($mdb) eq 'HASH') {
$record = $mdb;
}
else {
$record = $mdb->row_hash($mkey);
#::logDebug("used mkey=$mkey to select record=$record");
}
$record ||= $mdb->row_hash($item) if $view and $mdb;
#::logDebug("meta_record record=$record");
return undef if ! $record;
# Get additional settings from extended field, which is a serialized
# hash
my $hash;
if(! $record->{extended}) {
return undef if $extended_only;
}
else {
## From Vend::Util
$hash = get_option_hash($record->{extended});
$record = {} if $extended_only;
if(ref $hash eq 'HASH') {
@$record{keys %$hash} = values %$hash;
}
else {
undef $hash;
return undef if $extended_only;
}
}
# Allow view settings to be placed in the extended area
if($view and $hash and $hash->{view}) {
my $view_hash = $record->{view}{$view};
ref $view_hash
and @$record{keys %$view_hash} = values %$view_hash;
}
# Allow overlay of certain settings
if($overlay and $record->{overlay}) {
my $ol_hash = $record->{overlay}{$overlay};
Vend::Util::copyref($ol_hash, $record) if $ol_hash;
}
#::logDebug("return meta_record=" . ::uneval($record) );
return $record;
}
my $base_entry_value;
sub display {
my ($table,$column,$key,$opt) = @_;
if( ref($opt) ne 'HASH' ) {
$opt = get_option_hash($opt);
}
my $template = $opt->{type} eq 'hidden' ? '' : $opt->{template};
if($opt->{override}) {
$opt->{value} = defined $opt->{default} ? $opt->{default} : '';
}
if(! defined $opt->{value} and $table and $column and length($key)) {
$opt->{value} = tag_data($table, $column, $key);
$opt->{already_got_data} = 1;
}
my $mtab;
my $record;
my $no_meta = $opt->{ui_no_meta_display};
METALOOK: {
## No meta display wanted
last METALOOK if $no_meta;
## No meta display possible
$table and $column or $opt->{meta}
or last METALOOK;
## We get a metarecord directly, though why it would be here
## and not in options I don't know
if($opt->{meta} and ref($opt->{meta}) eq 'HASH') {
$record = $opt->{meta};
last METALOOK;
}
$mtab = $opt->{meta_table} || $::Variable->{UI_META_TABLE} || 'mv_metadata'
or last METALOOK;
my $meta = Vend::Data::database_exists_ref($mtab)
or do {
::logError("non-existent meta table: %s", $mtab);
undef $mtab;
last METALOOK;
};
my $view = $opt->{view} || $opt->{arbitrary};
## This is intended to trigger on the first access
if($table eq $mtab and $column eq $meta->config('KEY')) {
if($view and $opt->{value} !~ /::.+::/) {
$base_entry_value = ($opt->{value} =~ /^([^:]+)::(\w+)$/)
? $1
: $opt->{value};
}
else {
$base_entry_value = $opt->{value} =~ /(\w+)::/
? $1
: $opt->{value};
}
}
my (@tries) = "${table}::$column";
unshift @tries, "${table}::${column}::$key"
if length($key) and $opt->{specific};
my $sess = $Vend::Session->{mv_metadata} || {};
push @tries, { type => $opt->{type} }
if $opt->{type} || $opt->{label};
for my $metakey (@tries) {
## In case we were passed a meta record
last if $record = $sess->{$metakey} and ref $record;
$record = meta_record($metakey, $view, $meta)
and last;
}
}
my $w;
METAMAKE: {
last METAMAKE if $no_meta;
if( ! $record ) {
$record = { %$opt };
}
else {
## Here we allow override with the display tag, even with views and
## extended
my @override = qw/
append
attribute
db
callback_prescript
callback_postscript
class
default
extra
disabled
field
form
form_name
filter
height
help
help_url
id
label
js_check
lookup
lookup_exclude
lookup_query
maxlength
name
options
outboard
passed
pre_filter
prepend
table
type
type_empty
width
/;
for(@override) {
delete $record->{$_} if ! length($record->{$_});
next unless defined $opt->{$_};
$record->{$_} = $opt->{$_};
}
}
if($record->{type_empty} and length($opt->{value}) == 0) {
$record->{type} = $record->{type_empty};
}
else {
$record->{type} ||= $opt->{default_widget};
}
$record->{name} ||= $column;
#::logDebug("record now=" . ::uneval($record));
if($record->{options} and $record->{options} =~ /^[\w:,]+$/) {
#::logDebug("checking options");
PASS: {
my $passed = $record->{options};
if($passed eq 'tables') {
my @tables = $Tag->list_databases();
$record->{passed} = join (',', "=--none--", @tables);
}
elsif($passed =~ /^(?:filters|\s*codedef:+(\w+)(:+(\w+))?\s*)$/i) {
my $tag = $1 || 'filters';
my $mod = $3;
$record->{passed} = Vend::Util::codedef_options($tag, $mod);
}
elsif($passed =~ /^columns(::(\w*))?\s*$/) {
my $total = $1;
my $tname = $2 || $record->{db} || $table;
if ($total eq '::' and $base_entry_value) {
$tname = $base_entry_value;
}
$record->{passed} = join ",",
"=--none--",
$Tag->db_columns($tname),
;
}
elsif($passed =~ /^keys(::(\w+))?\s*$/) {
my $tname = $2 || $record->{db} || $table;
$record->{passed} = join ",",
"=--none--",
$Tag->list_keys($tname),
;
}
}
}
#::logDebug("checking for custom widget");
if ($record->{type} =~ s/^custom\s+//s) {
my $wid = lc $record->{type};
$wid =~ tr/-/_/;
$record->{attribute} ||= $column;
$record->{table} ||= $mtab;
$record->{rows} ||= $record->{height};
$record->{cols} ||= $record->{width};
$record->{field} ||= 'options';
$record->{name} ||= $column;
eval {
$w = $Tag->$wid($record->{name}, $opt->{value}, $record, $opt);
};
if($@) {
::logError("error using custom widget %s: %s", $wid, $@);
}
last METAMAKE;
}
$opt->{restrict_allow} ||= $record->{restrict_allow};
#::logDebug("formatting prepend/append/lookup_query name=$opt->{name} restrict_allow=$opt->{restrict_allow}");
for(qw/append prepend lookup_query/) {
next unless $record->{$_};
if($opt->{restrict_allow}) {
$record->{$_} = $Tag->restrict({
log => 'none',
enable => $opt->{restrict_allow},
disable => $opt->{restrict_deny},
}, $record->{$_});
}
else {
$record->{$_} = expand_values($record->{$_});
}
$record->{$_} = Vend::Util::resolve_links($record->{$_});
$record->{$_} =~ s/_UI_VALUE_/$opt->{value}/g;
$record->{$_} =~ /_UI_URL_VALUE_/
and do {
my $tmp = $opt->{value};
$tmp =~ s/(\W)/sprintf '%%%02x', ord($1)/eg;
$record->{$_} =~ s/_UI_URL_VALUE_/$tmp/g;
};
$record->{$_} =~ s/_UI_TABLE_/$table/g;
$record->{$_} =~ s/_UI_COLUMN_/$column/g;
$record->{$_} =~ s/_UI_KEY_/$key/g;
}
if($opt->{opts}) {
my $r = get_option_hash(delete $opt->{opts});
for my $k (keys %$r) {
$record->{$k} = $r->{$k};
}
}
#::logDebug("overriding defaults");
#::logDebug("passed=$record->{passed}") if $record->{debug};
my %things = (
attribute => $column,
cols => $opt->{cols} || $record->{width},
passed => $record->{options},
rows => $opt->{rows} || $record->{height},
value => $opt->{value},
applylocale => $opt->{applylocale},
);
while( my ($k, $v) = each %things) {
next if length $record->{$k};
next unless defined $v;
$record->{$k} = $v;
}
#::logDebug("calling Vend::Form with record=" . ::uneval($record));
if($record->{save_defaults}) {
my $sd = $Vend::Session->{meta_defaults} ||= {};
$sd = $sd->{"${table}::$column"} ||= {};
while (my ($k,$v) = each %$record) {
next if ref($v) eq 'CODE';
$sd->{$k} = $v;
}
}
$w = Vend::Form::display($record);
if($record->{filter}) {
$w .= qq{<input type="hidden" name="ui_filter:$record->{name}" value="};
$w .= $record->{filter};
$w .= '">';
}
}
if(! defined $w) {
my $text = $opt->{value};
my $iname = $opt->{name} || $column;
# Count lines for textarea
my $count;
$count = $text =~ s/(\r\n|\r|\n)/$1/g;
HTML::Entities::encode($text, $ESCAPE_CHARS::std);
my $size;
if ($count) {
$count++;
$count = 20 if $count > 20;
$w = <<EOF;
<textarea name="$iname" cols="60" rows="$count">$text</textarea>
EOF
}
elsif ($text =~ /^\d+$/) {
my $cur = length($text);
$size = $cur > 8 ? $cur + 1 : 8;
}
else {
$size = 60;
}
$w = <<EOF;
<input name="$iname" size="$size" value="$text">
EOF
}
my $array_return = wantarray;
#::logDebug("widget=$w");
# don't output label if widget is hidden form variable only
# and not an array type
undef $template if $w =~ /^\s*<input\s[^>]*type\s*=\W*hidden\b[^>]*>\s*$/i;
return $w unless $template || $opt->{return_hash} || $array_return;
if($template and $template !~ /\s/) {
$template = <<EOF;
<tr>
<td>
<b>\$LABEL\$</b>
</td>
<td valign="top">
<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>
</td>
</tr>
EOF
}
$record->{label} ||= $column;
my %sub = (
WIDGET => $w,
HELP => $opt->{applylocale}
? errmsg($record->{help})
: $record->{help},
META_URL => $opt->{meta_url},
HELP_URL => $record->{help_url},
LABEL => $opt->{applylocale}
? errmsg($record->{label})
: $record->{label},
);
#::logDebug("passed meta_url=$opt->{meta_url}");
$sub{HELP_EITHER} = $sub{HELP} || $sub{HELP_URL};
if($opt->{return_hash}) {
$sub{OPT} = $opt;
$sub{RECORD} = $record;
return \%sub;
}
elsif($array_return) {
return ($w, $sub{LABEL}, $sub{HELP}, $record->{help_url});
}
else {
# Strip the {TAG} {/TAG} pairs if nothing there
$template =~ s#\{([A-Z_]+)\}(.*?)\{/\1\}#$sub{$1} ? $2: '' #ges;
# Insert the TAG
$sub{HELP_URL} ||= 'javascript:void(0)';
$template =~ s/\$([A-Z_]+)\$/$sub{$1}/g;
#::logDebug("substituted template is: $template");
return $template;
}
}
sub tabbed_display {
my ($tit, $cont, $opt) = @_;
$opt ||= {};
my @colors;
$opt->{tab_bgcolor_template} ||= '#xxxxxx';
$opt->{tab_height} ||= '20'; $opt->{tab_width} ||= '120';
$opt->{panel_id} ||= 'mvpan';
$opt->{tab_horiz_offset} ||= '10';
$opt->{tab_vert_offset} ||= '8';
my $width_height;
$opt->{tab_style} ||= q{
text-align:center;
font-family: sans-serif;
line-height:150%;
font-size: smaller;
border:2px;
border-color:#999999;
border-style:outset;
border-bottom-style:none;
};
if($opt->{ui_style}) {
$opt->{panel_style} ||= q{
padding: 0;
};
$width_height = '';
}
else {
$opt->{panel_style} ||= q{
font-family: sans-serif;
font-size: smaller;
padding: 0;
border: 2px;
border-color:#999999;
border-style:outset;
};
$opt->{panel_height} ||= '600';
$opt->{panel_width} ||= '800';
$width_height = <<EOF;
width: $opt->{panel_width}px;
height: $opt->{panel_height}px;
EOF
}
$opt->{panel_shade} ||= 'e';
my @chars = reverse(0 .. 9, 'a' .. $opt->{panel_shade});
my $id = $opt->{panel_id};
my $vpf = $id . '_';
my $num_panels = scalar(@$cont);
my $tabs_per_row = int( $opt->{panel_width} / $opt->{tab_width}) || 1;
my $num_rows = POSIX::ceil( $num_panels / $tabs_per_row);
my $width = $opt->{panel_width};
my $height = $opt->{tab_height} * $num_rows + $opt->{panel_height};
my $panel_y;
my $int1;
my $int2;
if($opt->{ui_style}) {
$panel_y = 2;
$int1 = $int2 = 0;
}
else {
$panel_y =
$num_rows
* ($opt->{tab_height} - $opt->{tab_vert_offset})
+ $opt->{tab_vert_offset};
$int1 = $panel_y - 2;
$int2 = $opt->{tab_height} * $num_rows;
}
for(my $i = 0; $i < $num_panels; $i++) {
my $c = $opt->{tab_bgcolor_template} || '#xxxxxx';
$c =~ s/x/$chars[$i] || $opt->{panel_shade}/eg;
$colors[$i] = $c;
}
my $cArray = qq{var ${vpf}colors = ['} . join("','", @colors) . qq{'];};
#::logDebug("num rows=$num_rows");
my $out = <<EOF;
<script language="JavaScript">
<!--
var ${vpf}panelID = "$id"
var ${vpf}numDiv = $num_panels;
var ${vpf}numRows = $num_rows;
var ${vpf}tabsPerRow = $tabs_per_row;
var ${vpf}numLocations = ${vpf}numRows * ${vpf}tabsPerRow
var ${vpf}tabWidth = $opt->{tab_width}
var ${vpf}tabHeight = $opt->{tab_height}
var ${vpf}vOffset = $opt->{tab_vert_offset};
var ${vpf}hOffset = $opt->{tab_horiz_offset};
$cArray
var ${vpf}uptabs = new Array;
var ${vpf}dntabs = new Array;
var ${vpf}divLocation = new Array(${vpf}numLocations)
var ${vpf}newLocation = new Array(${vpf}numLocations)
for(var i=0; i<${vpf}numLocations; ++i) {
${vpf}divLocation[i] = i
${vpf}newLocation[i] = i
}
function ${vpf}getDiv(s,i) {
var div
if (document.layers) {
div = document.layers[${vpf}panelID].layers[panelID+s+i]
} else if (document.all && !document.getElementById) {
div = document.all[${vpf}panelID+s+i]
} else {
div = document.getElementById(${vpf}panelID+s+i)
}
return div
}
function ${vpf}setZIndex(div, zIndex) {
if (document.layers) div.style = div;
div.style.zIndex = zIndex
}
function ${vpf}updatePosition(div, newPos) {
${vpf}newClip=${vpf}tabHeight*(Math.floor(newPos/${vpf}tabsPerRow)+1)
if (document.layers) {
div.style=div;
div.clip.bottom=${vpf}newClip; // clip off bottom
} else {
div.style.clip="rect(0 auto "+${vpf}newClip+" 0)"
}
div.style.top = (${vpf}numRows-(Math.floor(newPos/${vpf}tabsPerRow) + 1)) * (${vpf}tabHeight-${vpf}vOffset)
div.style.left = (newPos % ${vpf}tabsPerRow) * ${vpf}tabWidth + (${vpf}hOffset * (Math.floor(newPos / ${vpf}tabsPerRow)))
}
function ${vpf}tripTab(n) {
// n is the ID of the division that was clicked
// firstTab is the location of the first tab in the selected row
var el;
for(var i = 0; i < ${vpf}dntabs.length; i++) {
el = document.getElementById('${vpf}td' + i);
if(el != undefined) {
el.innerHTML = ${vpf}dntabs[ i ];
el.style.backgroundColor = '#B4B0AA';
}
}
el = document.getElementById('${vpf}td' + n);
el.innerHTML = ${vpf}uptabs[ n ];
el.style.backgroundColor = '#D4D0C8';
// Set tab positions & zIndex
// Update location
var j = 1;
for(var i=0; i<${vpf}numDiv; ++i) {
var loc = ${vpf}newLocation[i]
var div = ${vpf}getDiv("panel",i)
if(i == n) {
${vpf}setZIndex(div, ${vpf}numLocations +1);
div.style.display = 'block';
div.style.backgroundColor = ${vpf}colors[0];
}
else {
${vpf}setZIndex(div, ${vpf}numLocations - loc)
div.style.display = 'none';
div.style.backgroundColor = ${vpf}colors[j++];
}
${vpf}divLocation[i] = loc
}
}
function ${vpf}selectTab(n) {
// n is the ID of the division that was clicked
// firstTab is the location of the first tab in the selected row
var firstTab = Math.floor(${vpf}divLocation[n] / ${vpf}tabsPerRow) * ${vpf}tabsPerRow
// newLoc is its new location
for(var i=0; i<${vpf}numDiv; ++i) {
// loc is the current location of the tab
var loc = ${vpf}divLocation[i]
// If in the selected row
if(loc >= firstTab && loc < (firstTab + ${vpf}tabsPerRow)) ${vpf}newLocation[i] = (loc - firstTab)
else if(loc < ${vpf}tabsPerRow) ${vpf}newLocation[i] = firstTab+(loc % ${vpf}tabsPerRow)
else ${vpf}newLocation[i] = loc
}
// Set tab positions & zIndex
// Update location
var j = 1;
for(var i=0; i<${vpf}numDiv; ++i) {
var loc = ${vpf}newLocation[i]
var div = ${vpf}getDiv("panel",i)
var tdiv = ${vpf}getDiv("tab",i)
if(i == n) {
${vpf}setZIndex(div, ${vpf}numLocations +1);
div.style.display = 'block';
tdiv.style.backgroundColor = ${vpf}colors[0];
div.style.backgroundColor = ${vpf}colors[0];
}
else {
${vpf}setZIndex(div, ${vpf}numLocations - loc)
div.style.display = 'none';
tdiv.style.backgroundColor = ${vpf}colors[j];
div.style.backgroundColor = ${vpf}colors[j++];
}
${vpf}divLocation[i] = loc
${vpf}updatePosition(tdiv, loc)
if(i == n) ${vpf}setZIndex(tdiv, ${vpf}numLocations +1)
else ${vpf}setZIndex(tdiv,${vpf}numLocations - loc)
}
}
//-->
</script>
<style type="text/css">
<!--
.${id}tab {
font-weight: bold;
width:$opt->{tab_width}px;
margin:0px;
height: ${int2}px;
position:relative;
$opt->{tab_style}
}
.${id}panel {
position:relative;
width: $opt->{panel_width}px;
height: $opt->{panel_height}px;
left:0px;
top:${int1}px;
margin:0px;
$opt->{panel_style}
}
-->
</style>
EOF
my $s1 = '';
my $s2 = '';
my $ibase = $Tag->image({
ui => $Vend::admin,
dir_only => 1,
secure => $Vend::admin && $::Variable->{UI_SECURE},
});
$opt->{clear_image} ||= 'bg.gif';
my $clear = "$ibase/$opt->{clear_image}";
my @dntabs;
my @uptabs;
for(my $i = 0; $i < $num_panels; $i++) {
my $zi = $num_panels - $i;
my $pnum = $i + 1;
my $left = (($i % $tabs_per_row)
* $opt->{tab_width}
+ ($opt->{tab_horiz_offset}
* (int($i / $tabs_per_row))));
my $top = ( $num_rows - (int($i / $tabs_per_row) + 1))
- ($opt->{tab_height} - $opt->{tab_vert_offset});
my $cliprect = $opt->{tab_height} * (int($i / $tabs_per_row) + 1);
$s1 .= <<EOF;
<div id="${id}panel$i"
class="${id}panel"
style="
background-color: $colors[$i];
z-index:$zi
">
$opt->{panel_prepend}
$cont->[$i]
$opt->{panel_append}
</div>
EOF
if($opt->{ui_style}) {
$s2 .= <<EOF;
<td class="subtabdown" id="${vpf}td$i">
EOF
$dntabs[$i] = <<EOF;
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="subtabdownleft"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td>
<td nowrap class="subtabdownfill"><a href="javascript:${vpf}tripTab($i,1)" class="subtablink">$tit->[$i]</a></td>
<td class="subtabdownright"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td>
</tr>
<tr>
<td colspan="3" class="darkshade"><img src="$clear" height="1"></td>
</tr>
<tr>
<td colspan="3" class="lightshade"><img src="$clear" height="1"></td>
</tr>
</table>
EOF
$s2 .= $dntabs[$i];
$uptabs[$i] = <<EOF;
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="subtableft"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td>
<td nowrap class="subtabfill"><a href="javascript:${vpf}tripTab($i,1)" class="subtablink">$tit->[$i]</a></td>
<td class="subtabright"><a href="javascript:${vpf}tripTab($i,1)"><img src="$clear" width="16" height="16" border="0"></a></td>
</tr>
<tr>
<td colspan="3" class="subtabfilllwr"><img src="$clear" height="1"></td>
</tr>
</table>
EOF
$s2 .= "</td>\n";
}
else {
$s1 .= <<EOF;
<div
onclick="${vpf}selectTab($i)"
id="${id}tab$i"
class="${id}tab"
style="
position: absolute;
background-color: $colors[$i];
cursor: pointer;
left: ${left}px;
top: ${top}px;
z-index:$zi;
clip:rect(0 auto $cliprect 0);
">
$tit->[$i]
</div>
EOF
}
}
my $start_index = $opt->{start_at_index} || 0;
$start_index += 0;
if($s2) {
$Tag->output_to('third_tabs', { name => 'third_tabs' }, $s2);
}
$out .= <<EOF;
<div style="
position: relative;
left: 0; top: 0; width: 100%; height: 100%;
z-index: 0;
">
$s1
EOF
if($s2) {
$out .= <<EOF;
<script>
EOF
for(my $i = 0; $i < @dntabs; $i++) {
$out .= "${vpf}uptabs[ $i ] = ";
$out .= $Tag->jsq($uptabs[$i]);
$out .= ";\n";
$out .= "${vpf}dntabs[ $i ] = ";
$out .= $Tag->jsq($dntabs[$i]);
$out .= ";\n";
}
$out .= <<EOF;
${vpf}tripTab($start_index);
</script>
EOF
}
else {
$out .= <<EOF;
<script>
${vpf}selectTab($start_index);
</script>
EOF
}
$out .= <<EOF;
</div>
EOF
}
sub tabbed_jui {
my ($tit, $cont, $opt) = @_;
$opt ||= {};
my @colors;
my $id = $opt->{jui_tabs};
my $num_panels = scalar(@$tit);
my $out = <<EOF;
<script>
\$(function() {
\$("#$id").tabs();
});
</script>
<div id="$id">
<ul>
EOF
my $s1 = '';
my $s2 = '';
for(my $i = 0; $i < $num_panels; $i++) {
my $tabid = "$id-$i";
$s1 .= qq{<li><a href="#$tabid">$tit->[$i]</a></li>\n};
my $len1 = length($cont->[$i]);
my $len2 = length($s2);
$s2 .= qq{<div id="$tabid"><table$opt->{panel_table_extra}>$cont->[$i]</table></div>};
my $len3 = length($s2);
}
$out .= $s1;
$out .= "</ul>\n";
$out .= $s2;
$out .= "</div>\n";
return $out;
}
my $tcount_all;
my %alias;
my %exclude;
my %outhash;
my @titles;
my @controls;
my $ctl_index = 0;
my @out;
sub ttag {
return 'TABLE_STD' . ++$tcount_all;
}
sub add_exclude {
my ($tag, $string) = @_;
#::logDebug("calling add_exclude tag='$tag' string='$string'");
return unless $string =~ /\S/;
$exclude{$tag} ||= ' ';
$exclude{$tag} .= "$string ";
}
sub col_chunk {
my $value = pop @_;
my $tag = shift @_;
my $exclude = shift @_;
my @others = @_;
$tag = "COLUMN_$tag";
#::logDebug("$tag content length=" . length($value));
if(exists $outhash{$tag}) {
my $col = $tag;
$col =~ s/^COLUMN_//;
my $msg = errmsg("Column '%s' defined twice, skipping second.", $col);
Vend::Tags->warnings($msg);
return;
}
$outhash{$tag} = $value;
if(@others) {
$alias{$tag} ||= [];
push @{$alias{$tag}}, @others;
}
my $ctl = $controls[$ctl_index] ||= [];
add_exclude($tag, $exclude) if $exclude;
return unless length($value);
push @$ctl, $tag;
return;
}
sub chunk_alias {
my $tag = shift;
$alias{$tag} ||= [];
push @{$alias{$tag}}, @_;
return;
}
sub chunk {
my $value = pop @_;
my $tag = shift @_;
my $exclude = shift @_;
my @others = @_;
die "duplicate tag settor $tag" if exists $outhash{$tag};
$outhash{$tag} = $value;
#::logDebug("$tag exclude=$exclude, content length=" . length($value));
if(@others) {
$alias{$tag} ||= [];
push @{$alias{$tag}}, @others;
}
add_exclude($tag, $exclude) if $exclude =~ /\S/;
return unless length($value);
push @out, $tag;
}
sub resolve_exclude {
my $exc = shift;
while(my ($k, $v) = each %exclude) {
my %seen;
my @things = grep /\S/ && ! $seen{$_}++, split /\s+/, $v;
#::logDebug("examining $k for $v");
for my $thing (@things) {
if($thing =~ s/^[^A-Z]//) {
#::logDebug("examining $v for $thing!=$exc->{$thing}");
$outhash{$k} = '' unless $exc->{$thing};
}
else {
#::logDebug("examining $v for $thing=$exc->{$thing}");
$outhash{$k} = '' if $exc->{$thing};
}
}
}
}
sub editor_init {
undef $base_entry_value;
## Why?
Vend::Interpolate::init_calc() if ! $Vend::Calc_initialized;
@out = ();
@controls = ();
@titles = ();
%outhash = ();
%exclude = ();
%alias = ();
$tcount_all = 0;
$ctl_index = 0;
}
my %o_default_length = (
border_cell_class => 'cborder',
widget_cell_class => 'cwidget',
label_cell_class => 'clabel',
data_cell_class => 'cdata',
help_cell_class => 'chelp',
break_cell_class_first => 'cbreakfirst',
break_cell_class => 'cbreak',
spacer_row_class => 'rspacer',
break_row_class => 'rbreak',
title_row_class => 'rmarq',
data_row_class => 'rnorm',
ok_button_style => 'font-weight: bold; width: 40px; text-align: center',
);
my %o_default_var = (qw/
color_fail UI_CONTRAST
color_success UI_C_SUCCESS
/);
my %o_default_defined = (
mv_update_empty => 1,
restrict_allow => 'page area var',
);
my %o_default = (
action => 'set',
wizard_next => 'return',
help_anchor => 'help',
wizard_cancel => 'back',
across => 1,
color_success => '#00FF00',
color_fail => '#FF0000',
spacer_height => 1,
border_height => 1,
clear_image => 'bg.gif',
table_width => '100%',
);
# Build maps for ui_te_* option pass
my @cgi_opts = qw/
append
check
database
default
disabled
extra
field
filter
height
help
help_url
label
lookup
options
outboard
override
passed
pre_filter
prepend
template
widget
width
/;
my @hmap;
for(@cgi_opts) {
push @hmap, [ qr/ui_te_$_:/, $_ ];
}
my %ignore_cgi = qw/
item_id 1
item_id_left 1
mv_pc 1
mv_action 1
mv_todo 1
mv_ui 1
mv_data_table 1
mv_session_id 1
mv_nextpage 1
ui_sequence_edit 1
/;
sub save_cgi {
my $ref = {};
my @k;
if($CGI::values{save_cgi}) {
@k = split /\0/, $CGI::values{save_cgi};
}
else {
@k = grep ! $ignore_cgi{$_}, keys %CGI::values;
}
# Can be an array because of produce_hidden
$ref->{save_cgi} = \@k;
for(@k) {
$ref->{$_} = $CGI::values{$_};
}
return $ref;
}
sub produce_hidden {
my ($key, $val) = @_;
return unless length $val;
my @p; # pieces of var
my @o; # output
if(ref($val) eq 'ARRAY') {
@p = @$val;
}
else {
@p = split /\0/, $val;
}
for(@p) {
s/"/&quot;/g;
push @o, qq{<input type="hidden" name="$key" value="$_">\n};
}
return join "", @o;
}
sub resolve_options {
my ($opt, $CGI, $data) = @_;
# This may be passed by the caller, but is normally from the form
# or URL
$CGI ||= \%CGI::values;
my $table = $opt->{mv_data_table};
my $key = $opt->{item_id};
$table = $CGI->{mv_data_table}
if ! $table and $opt->{cgi} and $CGI->{mv_data_table};
$opt->{table} = $opt->{mv_data_table} = $table;
# First we see if something has created a big options munge
# for us
if($opt->{all_opts}) {
#::logDebug("all_opts being brought in...=" . ::uneval($opt->{all_opts}));
if(ref($opt->{all_opts}) eq 'HASH') {
#::logDebug("all_opts being brought in...");
my $o = $opt->{all_opts};
for (keys %$o ) {
$opt->{$_} = $o->{$_};
}
}
else {
my $o = meta_record($opt->{all_opts});
#::logDebug("all_opts being brought in text, o=$o");
if($o) {
for (keys %$o ) {
$opt->{$_} = $o->{$_};
}
}
else {
logError("%s: improper option %s, must be %s, was %s.",
'table_editor',
'all_opts',
'hash',
ref $opt->{all_opts},
);
}
}
#::logDebug("options now=" . ::uneval($opt));
}
my @mapdirect = qw/
back_button_class
back_button_style
border_cell_class
border_height
bottom_buttons
break_cell_class
break_cell_style
break_row_class
break_row_style
button_delete
cancel_button_class
cancel_button_style
clear_image
data_cell_class
data_cell_style
data_row_class
data_row_style
default_widget
delete_button_class
delete_button_style
display_type
file_upload
form_extra
form_prepend
help_anchor
help_cell_class
help_cell_style
image_meta
include_before
include_form
include_form_expand
include_form_interpolate
intro_text
label_cell_class
label_cell_style
left_width
link_auto_number
link_before
link_blank_auto
link_extra
link_fields
link_key
link_label
link_no_blank
link_row_qual
link_rows_blank
link_sort
link_table
link_template
link_view
mv_auto_export
mv_blob_field
mv_blob_label
mv_blob_nick
mv_blob_pointer
mv_blob_title
mv_data_decode
mv_data_table
mv_update_empty
next_button_class
next_button_style
no_meta
nodelete
ok_button_class
ok_button_style
output_map
panel_class
panel_height
panel_id
panel_shade
panel_style
panel_width
reset_button_class
reset_button_style
restrict_allow
spacer_height
spacer_row_class
spacer_row_style
start_at
tab_bgcolor_template
tab_cellpadding
tab_cellspacing
tab_class
tab_height
tab_horiz_offset
tab_style
tab_vert_offset
tab_width
tabbed
jui_tabs
table_height
table_width
title_row_class
title_row_style
top_buttons
ui_break_before
ui_break_before_label
ui_data_fields
ui_data_fields_all
ui_data_key_name
ui_delete_box
ui_display_only
ui_hide_key
ui_meta_specific
ui_meta_view
ui_new_item
ui_nextpage
ui_no_meta_display
ui_profile
view_from
widget_cell_class
widget_cell_style
widget_class
xhtml
/;
if($opt->{cgi}) {
for(@mapdirect) {
next if ! defined $CGI->{$_};
$opt->{$_} = $CGI->{$_};
}
my @cgi = keys %{$CGI};
foreach my $row (@hmap) {
my @keys = grep $_ =~ $row->[0], @cgi;
for(@keys) {
/^ui_\w+:(\S+)/
and $opt->{$row->[1]}{$1} = $CGI->{$_};
}
}
}
#::logDebug("no_meta_display=$opt->{ui_no_meta_display}");
my $tmeta;
if($opt->{no_table_meta} || $opt->{ui_no_meta_display}) {
$tmeta = {};
}
else {
$tmeta = meta_record($table, $opt->{ui_meta_view}) || {};
}
$opt->{view_from} ||= $tmeta->{view_from};
my $baseopt;
if($opt->{no_base_meta} || $opt->{ui_no_meta_display}) {
$baseopt = {};
}
else {
$baseopt = meta_record('table-editor') || {};
delete $baseopt->{extended};
}
if( ! $opt->{ui_meta_view}
and $opt->{view_from}
and $data
and ! $opt->{ui_no_meta_display}
and $opt->{ui_meta_view} = $data->{$opt->{view_from}}
)
{
$tmeta = meta_record($table, $opt->{ui_meta_view}) || {};
}
# This section checks the passed options and converts them from
# strings to refs if necessary
FORMATS: {
no strict 'refs';
my $ref;
for(qw/
append
default
disabled
database
error
extra
field
filter
form
height
help
help_url
label
lookup
lookup_query
js_check
maxlength
meta
options
outboard
override
passed
pre_filter
prepend
template
wid_href
widget
width
/ )
{
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;
}
}
for(grep length($baseopt->{$_}), @mapdirect) {
#::logDebug("checking baseopt->{$_}, baseopt=$baseopt->{$_} tmeta=$tmeta->{$_}");
$tmeta->{$_} = $baseopt->{$_} unless length $tmeta->{$_};
}
for(grep defined $tmeta->{$_}, @mapdirect) {
#::logDebug("checking tmeta->{$_}, tmeta=$tmeta->{$_} opt=$opt->{$_}");
#::logDebug("opt->{$_} is " . (defined $opt->{$_} ? 'defined' : 'undefined'));
$opt->{$_} = $tmeta->{$_} unless defined $opt->{$_};
}
if($opt->{cgi}) {
my @extra = qw/
item_id
item_id_left
ui_clone_id
ui_clone_tables
ui_sequence_edit
/;
for(@extra) {
next if ! defined $CGI->{$_};
$opt->{$_} = $CGI->{$_};
}
}
if($opt->{wizard}) {
$opt->{noexport} = 1;
$opt->{next_text} = 'Next -->' unless $opt->{next_text};
$opt->{back_text} = '<-- Back' unless $opt->{back_text};
}
else {
$opt->{next_text} = "Ok" unless $opt->{next_text};
}
$opt->{cancel_text} = 'Cancel' unless $opt->{cancel_text};
for(qw/ next_text cancel_text back_text/ ) {
$opt->{$_} = errmsg($opt->{$_});
}
if($opt->{wizard} || $opt->{notable} and ! $opt->{table}) {
$opt->{table} = 'mv_null';
$Vend::Database{mv_null} =
bless [
{},
undef,
[ 'code', 'value' ],
[ 'code' => 0, 'value' => 1 ],
0,
{ },
], 'Vend::Table::InMemory';
}
# resolve form defaults
while( my ($k, $v) = each %o_default_var) {
$opt->{$k} ||= $::Variable->{$v};
}
while( my ($k, $v) = each %o_default_length) {
$opt->{$k} = $v if ! length($opt->{$k});
}
while( my ($k, $v) = each %o_default_defined) {
$opt->{$k} = $v if ! defined($opt->{$k});
}
while( my ($k, $v) = each %o_default) {
$opt->{$k} ||= $v;
}
if (! $opt->{inner_table_width}) {
if ($opt->{table_width} =~ /^\d+$/) {
$opt->{inner_table_width} = $opt->{table_width} - 2;
}
elsif($opt->{table_width} =~ /\%/) {
$opt->{inner_table_width} = '100%';
}
else {
$opt->{inner_table_width} = $opt->{table_width};
}
}
if (! $opt->{inner_table_height}) {
if ($opt->{table_height} =~ /^\d+$/) {
$opt->{inner_table_height} = $opt->{table_height} - 2;
}
elsif($opt->{table_height} =~ /\%/) {
$opt->{inner_table_height} = '100%';
}
else {
$opt->{inner_table_height} = $opt->{table_height};
}
}
if(! $opt->{left_width}) {
if($opt->{table_width} eq '100%') {
$opt->{left_width} = 150;
}
else {
$opt->{left_width} = '30%';
}
}
if(my $dt = $opt->{display_type}) {
my $sub = $Display_options{$dt};
$sub and ref($sub) eq 'CODE' and $sub->($opt);
}
# init the row styles
foreach my $rtype (qw/data break combo spacer title/) {
my $mainp = $rtype . '_row_extra';
my $thing = '';
for my $ptype (qw/class style align valign width/) {
my $parm = $rtype . '_row_' . $ptype;
$opt->{$parm} ||= $tmeta->{$parm};
if(defined $opt->{$parm}) {
$thing .= qq{ $ptype="$opt->{$parm}"};
}
}
$opt->{$mainp} ||= $tmeta->{$mainp};
if($opt->{$mainp}) {
$thing .= " " . $opt->{$mainp};
}
$opt->{$mainp} = $thing;
}
# Init the cell styles
for my $ctype (qw/label data widget help break/) {
my $mainp = $ctype . '_cell_extra';
my $thing = '';
for my $ptype (qw/class style align valign width/) {
my $parm = $ctype . '_cell_' . $ptype;
$opt->{$parm} ||= $tmeta->{$parm};
if(defined $opt->{$parm}) {
$thing .= qq{ $ptype="$opt->{$parm}"};
}
}
$opt->{$mainp} ||= $tmeta->{$mainp};
if($opt->{$mainp}) {
$thing .= " " . $opt->{$mainp};
}
$opt->{$mainp} = $thing;
}
# Init the button styles
for my $ctype (qw/ok next back cancel delete reset/) {
my $mainp = $ctype . '_button_extra';
my $thing = '';
for my $ptype (qw/class style/) {
my $parm = $ctype . '_button_' . $ptype;
$opt->{$parm} ||= $tmeta->{$parm};
if(defined $opt->{$parm}) {
$thing .= qq{ $ptype="$opt->{$parm}"};
}
}
$opt->{$mainp} ||= $tmeta->{$mainp};
if($opt->{$mainp}) {
$thing .= " " . $opt->{$mainp};
}
$opt->{$mainp} = $thing;
}
$opt->{ui_data_fields} ||= $opt->{ui_wizard_fields};
###############################################################
# 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};
}
#::logDebug("fields were=$opt->{ui_data_fields}");
$opt->{ui_data_fields} =~ s/\r\n/\n/g;
$opt->{ui_data_fields} =~ s/\r/\n/g;
$opt->{ui_data_fields} =~ s/^[ \t]+//mg;
$opt->{ui_data_fields} =~ s/[ \t]+$//mg;
if($opt->{ui_data_fields} =~ /\n\n/) {
my @breaks;
my @break_labels;
my $fstring = "\n\n$opt->{ui_data_fields}";
while ($fstring =~ s/\n+(?:\n[ \t]*=(.*?)(\*?))?\n+[ \t]*(\w[:.\w]+)/\n$3/) {
push @breaks, $3;
$opt->{start_at} ||= $3 if $2;
push @break_labels, "$3=$1" if $1;
}
$opt->{ui_break_before} = join(" ", @breaks)
if ! $opt->{ui_break_before};
$opt->{ui_break_before_label} = join(",", @break_labels)
if ! $opt->{ui_break_before_label};
while($fstring =~ s/\n(.*)[ \t]*\*/\n$1/) {
$opt->{focus_at} = $1;
}
$opt->{ui_data_fields} = $fstring;
}
$opt->{ui_data_fields} ||= $opt->{mv_data_fields};
$opt->{ui_data_fields} =~ s/^[\s,\0]+//;
$opt->{ui_data_fields} =~ s/[\s,\0]+$//;
#::logDebug("fields now=$opt->{ui_data_fields}");
#### This code is also in main editor routine, change there too!
my $cells_per_span = $opt->{cell_span} || 2;
####
## Visual field layout
if($opt->{ui_data_fields} =~ /[\w:.]+[ \t,]+\w+.*\n\w+/) {
my $cs = $opt->{colspan} ||= {};
my @things = split /\n/, $opt->{ui_data_fields};
my @rows;
my $max = 0;
for(@things) {
my @cols = split /[\s\0,]+/, $_;
my $cnt = scalar(@cols);
$max = $cnt if $cnt > $max;
push @rows, \@cols;
}
$opt->{across} = $max;
for(@rows) {
my $cnt = scalar(@$_);
if ($cnt < $max) {
my $name = $_->[-1];
$cs->{$name} = (($max - $cnt) * $cells_per_span) + 1;
}
}
}
#### This code is also in main editor routine, change there too!
my $rowdiv = $opt->{across} || 1;
my $rowcount = 0;
my $span = $rowdiv * $cells_per_span;
####
# Make standard fixed rows
$opt->{spacer_row} = <<EOF;
<tr$opt->{spacer_row_extra}>
<td colspan="$span" $opt->{spacer_row_extra}><img src="$opt->{clear_image}" width="1" height="$opt->{spacer_height}" alt="x"></td>
</tr>
EOF
$opt->{mv_nextpage} = $Global::Variable->{MV_PAGE}
if ! $opt->{mv_nextpage};
$opt->{form_extra} =~ s/^\s*/ /
if $opt->{form_extra};
$opt->{form_extra} ||= '';
$opt->{form_extra} .= qq{ name="$opt->{form_name}"}
if $opt->{form_name};
$opt->{form_extra} .= qq{ target="$opt->{form_target}"}
if $opt->{form_target};
$opt->{enctype} = $opt->{file_upload} ? ' enctype="multipart/form-data"' : '';
}
# UserTag table-editor Order mv_data_table item_id
# UserTag table-editor addAttr
# UserTag table-editor AttrAlias clone ui_clone_id
# UserTag table-editor AttrAlias table mv_data_table
# UserTag table-editor AttrAlias fields ui_data_fields
# UserTag table-editor AttrAlias mv_data_fields ui_data_fields
# UserTag table-editor AttrAlias key item_id
# UserTag table-editor AttrAlias view ui_meta_view
# UserTag table-editor AttrAlias profile ui_profile
# UserTag table-editor AttrAlias email_fields ui_display_only
# UserTag table-editor hasEndTag
# UserTag table-editor MapRoutine Vend::Table::Editor::editor
sub editor {
my ($table, $key, $opt, $overall_template) = @_;
show_times("begin table editor call item_id=$key") if $Global::ShowTimes;
#::logDebug("overall_template=$overall_template\nin=$opt->{overall_template}");
use vars qw/$Tag/;
editor_init($opt);
my @messages;
my @errors;
my $pass_return_to;
my $hidden = $opt->{hidden} ||= {};
#::logDebug("key at beginning: $key");
$opt->{mv_data_table} = $table if $table;
$opt->{table} = $opt->{mv_data_table};
$opt->{ui_meta_view} ||= $CGI->{ui_meta_view} if $opt->{cgi};
$key ||= $opt->{item_id};
if($opt->{cgi}) {
$key ||= $CGI->{item_id};
unless($opt->{ui_multi_key} = $CGI->{ui_multi_key}) {
$opt->{item_id_left} ||= $CGI::values{item_id_left};
$opt->{ui_sequence_edit} ||= $CGI::values{ui_sequence_edit};
}
}
if($opt->{ui_sequence_edit} and ! $opt->{ui_multi_key}) {
delete $opt->{ui_sequence_edit};
my $left = delete $opt->{item_id_left};
if(! $key) {
#::logDebug("No key, getting from $left");
if($left =~ s/(.*?)[\0,]// ) {
$key = $opt->{item_id} = $1;
$hidden->{item_id_left} = $left;
$hidden->{ui_sequence_edit} = 1;
}
elsif($left) {
$key = $opt->{item_id} = $left;
}
#::logDebug("No key, left now $left");
}
elsif($left) {
#::logDebug("Key, leaving left $left");
$hidden->{item_id_left} = $left;
$hidden->{ui_sequence_edit} = 1;
}
}
$opt->{item_id} = $key;
$pass_return_to = save_cgi() if $hidden->{ui_sequence_edit};
my $data;
my $exists;
my $db;
my $multikey;
## Try and sneak a peek at the data so we can determine views and
## maybe some other stuff -- we definitely need table/key or a
## clone id
unless($opt->{notable}) {
# From Vend::Data
my $tab = $table || $opt->{mv_data_table} || $CGI->{mv_data_table};
my $key = $opt->{item_id} || $CGI->{item_id};
$db = database_exists_ref($tab);
if($db) {
$multikey = $db->config('COMPOSITE_KEY');
if($multikey and $key !~ /\0/) {
$key =~ s/-_NULL_-/\0/g;
}
if($opt->{ui_clone_id} and $db->record_exists($opt->{ui_clone_id})) {
$data = $db->row_hash($opt->{ui_clone_id});
}
elsif ($key and $db->record_exists($key)) {
$data = $db->row_hash($key);
$exists = 1;
}
if(! $exists and $multikey) {
$data = {};
eval {
my @inits = split /\0/, $key;
for(@{$db->config('_Key_columns')}) {
$data->{$_} = shift @inits;
}
};
}
}
}
#::logDebug("table editor multikey/key/data=\n" . ::uneval($multikey, $key, $data));
my $regin = $opt->{all_opts} ? 1 : 0;
resolve_options($opt, undef, $data);
$Trailer = $opt->{xhtml} ? '/' : '';
if($regin) {
## Must reset these in case they get set from all_opts.
$hidden = $opt->{hidden};
}
$overall_template = $opt->{overall_template}
if $opt->{overall_template};
$table = $opt->{table};
$key = $opt->{item_id};
if($opt->{save_meta}) {
$::Scratch->{$opt->{save_meta}} = uneval($opt);
}
#::logDebug("key after resolve_options: $key");
#::logDebug("cell_span=$opt->{cell_span}");
#### This code is also in resolve_options routine, change there too!
my $rowdiv = $opt->{across} || 1;
my $cells_per_span = $opt->{cell_span} || 2;
my $rowcount = 0;
my $span = $rowdiv * $cells_per_span;
####
my $oddspan = $span - 1;
my $def = $opt->{default_ref} || $::Values;
my $append = $opt->{append};
my $check = $opt->{check};
my $class = $opt->{class} || {};
my $database = $opt->{database};
my $default = $opt->{default};
my $disabled = $opt->{disabled};
my $error = $opt->{error};
my $extra = $opt->{extra};
my $field = $opt->{field};
my $filter = $opt->{filter};
my $form = $opt->{form};
my $height = $opt->{height};
my $help = $opt->{help};
my $help_url = $opt->{help_url};
my $id = $opt->{id};
my $label = $opt->{label};
my $wid_href = $opt->{wid_href};
my $lookup = $opt->{lookup};
my $lookup_query = $opt->{lookup_query};
my $meta = $opt->{meta};
my $js_check = $opt->{js_check};
my $maxlength = $opt->{maxlength};
my $opts = $opt->{opts};
my $options = $opt->{options};
my $outboard = $opt->{outboard};
my $override = $opt->{override};
my $passed = $opt->{passed};
my $pre_filter = $opt->{pre_filter};
my $prepend = $opt->{prepend};
my $template = $opt->{template};
my $widget = $opt->{widget};
my $width = $opt->{width};
my $colspan = $opt->{colspan} || {};
my $blabel = $opt->{blabel};
my $elabel = $opt->{elabel};
my $mlabel = '';
my $hidden_all = $opt->{hidden_all} ||= {};
#::logDebug("hidden_all=" . ::uneval($hidden_all));
my $ntext;
my $btext;
my $ctext;
if($pass_return_to) {
delete $::Scratch->{$opt->{next_text}};
}
elsif (! $opt->{wizard} and ! $opt->{nosave}) {
$ntext = $Tag->return_to('click', 1);
$ctext = $ntext . "\nmv_todo=back";
}
else {
if($opt->{action_click}) {
$ntext = <<EOF;
mv_todo=$opt->{wizard_next}
ui_wizard_action=Next
mv_click=$opt->{action_click}
EOF
}
else {
$ntext = <<EOF;
mv_todo=$opt->{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=$opt->{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=$opt->{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}, $ESCAPE_CHARS::std);
$::Scratch->{$opt->{next_text}} = $ntext if $ntext;
$::Scratch->{$opt->{cancel_text}} = $ctext if $ctext;
$::Scratch->{$opt->{back_text}} = $btext if $btext;
undef $opt->{auto_secure} if $opt->{cgi};
### 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} || "&update=yes\n";
if ($prof =~ s/^\*//) {
# special notation ui_profile="*whatever" means
# use automatic checklist-related profile
my $name = $prof;
$prof = $::Scratch->{"profile_$name"} || "&update=yes\n";
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
. "}; return 1\n"
. $prof;
## Un-confuse vi }
$opt->{ui_profile_success} = "&set=step_$name 1";
}
}
my $success = $opt->{ui_profile_success};
# make sure profile so far ends with a newline so we can add more
$prof .= "\n" unless $prof =~ /\n\s*\z/;
if(ref $check) {
while ( my($k, $v) = each %$check ) {
next unless length $v;
$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";
}
}
## Enable individual widget checks
$::Scratch->{mv_individual_profile} = 1;
## Call the profile in the form
$opt->{hidden}{mv_form_profile} = 'ui_profile';
my $fail = $opt->{mv_failpage} || $Global::Variable->{MV_PAGE};
# watch out for early interpolation here!
$::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};
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;
}
}
# the following should already be interpolated by the table-editor tag
# before going into scratch ui_profile
return <<'EOP';
$prof
&fail=$fail
&fatal=1
$success
mv_form_profile=mandatory
&set=mv_todo $opt->{action}
EOP
[/perl]
EOF
$opt->{blabel} = '<span style="font-weight: normal">';
$opt->{elabel} = '</span>';
$mlabel = ($opt->{message_label} || '&nbsp;&nbsp;&nbsp;'
. errmsg('<b>Bold</b> fields are required'));
$have_errors = $Tag->error( {
all => 1,
show_var => $error_show_var,
show_error => 1,
joiner => "<br$Vend::Xtrailer>",
keep => 1}
);
if($opt->{all_errors} and $have_errors) {
my $title = $opt->{all_errors_title} || errmsg('Errors');
my $style = $opt->{all_errors_style} || "color: $opt->{color_fail}";
my %hash = (
title => $opt->{all_errors_title} || errmsg('Errors'),
style => $opt->{all_errors_style} || "color: $opt->{color_fail}",
errors => $have_errors,
);
my $tpl = $opt->{all_errors_template} || <<EOF;
<p>{TITLE}:
<blockquote style="{STYLE}">{ERRORS}</blockquote>
</p>
EOF
$mlabel .= tag_attr_list($tpl, \%hash, 'uc');
}
}
### end build of error checking
my $die = sub {
::logError(@_);
$::Scratch->{ui_error} .= "<BR>\n" if $::Scratch->{ui_error};
$::Scratch->{ui_error} .= ::errmsg(@_);
return undef;
};
unless($opt->{notable}) {
# From Vend::Data
$db = database_exists_ref($table)
or return $die->("table-editor: bad table '%s'", $table);
}
$opt->{ui_data_fields} =~ s/[,\0\s]+/ /g;
if($opt->{ui_wizard_fields}) {
$opt->{ui_display_only} = $opt->{ui_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();
}
}
my $keycol = $opt->{ui_data_key_name};
$keycol ||= $db->config('KEY') if !$opt->{notable};
###############################################################
my $linecount;
CANONCOLS: {
my (@cols, %colseen);
for (split /[,\0\s]/, $opt->{ui_data_fields}) {
next if $colseen{$_}++;
push (@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_NEXT')) {
my $kn = $db->config('KEY');
my $ary = $db->query("select max($kn) from $table");
eval {
$key = $ary->[0][0];
};
$key ||= 'XXXXXX01';
if($key =~ /(.*[^A-Za-z0-9])([A-Za-z0-9]*)$/) {
my $non = $1;
my $word = $2;
$word++;
$key = $non . $word;
}
else {
$key++;
}
}
elsif(! $db->config('_Auto_number') and ! $db->config('AUTO_SEQUENCE')) {
$db->config('AUTO_NUMBER', '000001');
$key = $db->autonumber($key);
}
else {
$key = '';
$opt->{mv_data_auto_number} = 1;
$key_message = errmsg('(new key will be assigned if left blank)');
}
}
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 = $Vend::Interpolate::safe_safe->reval($bstring);
if($@) {
push @errors, ::errmsg("error reading blob data: %s", $@);
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;
unshift @labels, '';
my $extra = '';
for my $k (keys %$hidden_all) {
my $v = $hidden_all->{$k};
if(ref($v) eq 'ARRAY') {
for(@$v) {
$extra .= "\n$k=$_";
}
}
else {
$extra .= "\n$k=$v";
}
}
for my $key (@labels) {
my $ref;
my $lab;
if($key) {
$ref = $blob->{$key};
$lab = $ref->{$opt->{mv_blob_label} || 'name'};
}
else {
$key = '';
$lab = '--' . errmsg('none') . '--';
$ref = {};
}
if($lab) {
$lab =~ s/,/&#44/g;
$wid_data{$key} = "$key=$key - $lab";
next unless $key;
$url_data{$key} = $Tag->page( {
href => $Global::Variable->{MV_PAGE},
form => "
item_id=$opt->{item_id}
mv_blob_nick=$key$extra
",
});
$url_data{$key} .= "$key - $lab</a><br$Trailer>";
}
else {
$wid_data{$key} = $key;
next unless $key;
$url_data{$key} = $Tag->page( {
href => $Global::Variable->{MV_PAGE},
form => "
item_id=$opt->{item_id}
mv_blob_nick=$key$extra
",
});
$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 = display(undef, undef, undef, {
name => 'mv_blob_nick',
type => $opt->{ui_blob_widget} || 'combo',
filter => 'nullselect',
value => $opt->{mv_blob_nick},
passed => join (",", @wid_data{ sort keys %wid_data }) || 'default',
});
my $msg1 = errmsg('Save to');
my $msg2 = errmsg('Save to book 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" class="$opt->{widget_class}" value="1"$checked>&nbsp;$msg2</small>
EOF
}
$blob_widget = <<EOF unless $opt->{ui_blob_hidden};
<tr$opt->{data_row_extra}>
<td width="$opt->{left_width}"$opt->{label_cell_extra}>
<small>$opt->{mv_blob_title}<br>
$loaded_from
</td>
<td$opt->{data_cell_extra}>
$blob_widget&nbsp;
</td>
</tr>
<tr>
<td colspan="$span"$opt->{border_cell_extra}><img src="$opt->{clear_image}" width="1" height="$opt->{border_height}" alt="x"></td>
</tr>
EOF
if($opt->{mv_blob_nick}) {
delete $opt->{force_defaults};
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';
}
my $url_base = $opt->{secure} ? $Vend::Cfg->{SecureURL} : $Vend::Cfg->{VendURL};
if(! $opt->{href}) {
$opt->{href} = $opt->{mv_nextpage};
$opt->{hidden}{mv_ui} = 1
if $Vend::admin and ! defined $opt->{hidden}{mv_ui};
$opt->{hidden}{mv_action} = $opt->{action};
}
$opt->{href} = "$url_base/$opt->{href}"
if $opt->{href} !~ m{^(https?:|)/};
$opt->{method} = $opt->{get} ? 'GET' : 'POST';
my $wo = $opt->{widgets_only};
my $restrict_begin;
my $restrict_end;
if($opt->{reparse} and ! $opt->{promiscuous}) {
$restrict_begin = qq{[restrict allow="$opt->{restrict_allow}"]};
$restrict_end = '[/restrict]';
}
no strict 'subs';
chunk ttag(), $restrict_begin;
chunk 'FORM_BEGIN', 'OUTPUT_MAP', <<EOF;
<form method="$opt->{method}" action="$opt->{href}"$opt->{enctype}$opt->{form_extra}>
EOF
my $prescript_marker = $#out;
$hidden->{mv_click} = $opt->{process_filter};
$hidden->{mv_todo} = $opt->{action};
$hidden->{mv_nextpage} = $opt->{mv_nextpage};
$hidden->{mv_data_table} = $table;
$hidden->{mv_data_key} = $keycol;
if($opt->{cgi}) {
$hidden->{mv_return_table} = $CGI->{mv_return_table} || $table;
}
else {
$hidden->{mv_return_table} = $table;
}
chunk 'HIDDEN_ALWAYS', 'OUTPUT_MAP', <<EOF;
<input type="hidden" name="mv_session_id" value="$Vend::Session->{id}">
<input type="hidden" name="mv_click" value="process_filter">
EOF
my @opt_set = (qw/
ui_meta_specific
ui_hide_key
ui_meta_view
ui_new_item
ui_data_decode
mv_blob_field
mv_blob_label
mv_blob_title
mv_blob_pointer
mv_update_empty
mv_data_auto_number
mv_data_function
/);
for my $k (@opt_set) {
$opt->{hidden}{$k} = $opt->{$k};
}
if($pass_return_to) {
while( my($k, $v) = each %$pass_return_to) {
next if defined $opt->{hidden}{$k};
$opt->{hidden}{$k} = $pass_return_to->{$k};
}
}
if($opt->{mailto}) {
$opt->{mailto} =~ s/\s+/ /g;
$::Scratch->{mv_email_enable} = $opt->{mailto};
$opt->{hidden}{mv_data_email} = 1;
}
$Vend::Session->{ui_return_stack} ||= [];
if($opt->{cgi} and ! $pass_return_to) {
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} );
}
chunk 'RETURN_TO', '', $Tag->return_to(); # unless $wo;
#::logDebug("return-to stack = " . ::uneval($r_ary));
}
if(ref $opt->{hidden} or ref $opt->{hidden_all}) {
my ($hk, $hv);
my @o;
while ( ($hk, $hv) = each %$hidden ) {
##if new item, get mv_nextpage from radio buttons
if($opt->{ui_new_item}){
next if $hk =~ /mv_nextpage/;
push @o, produce_hidden($hk, $hv);
}
else {
push @o, produce_hidden($hk, $hv);
}
}
while ( ($hk, $hv) = each %$hidden_all ) {
push @o, produce_hidden($hk, $hv);
}
chunk 'HIDDEN_USER', 'OUTPUT_MAP', join("", @o); # unless $wo;
}
if($opt->{tabbed}) {
$opt->{table_width} ||= ($opt->{panel_width} || 800) + 10;
$opt->{table_height} ||= ($opt->{panel_height} || 600) + 10;
$opt->{inner_table_width} ||= ($opt->{panel_width} || 800);
$opt->{inner_table_height} ||= ($opt->{panel_height} || 600);
}
chunk ttag(), <<EOF; # unless $wo;
<table class="touter" border="0" cellspacing="0" cellpadding="0" width="$opt->{table_width}" height="$opt->{table_height}">
<tr>
<td valign="top">
<table class="tinner" width="$opt->{inner_table_width}" height="$opt->{inner_table_height}" cellspacing="0" cellmargin="0" cellpadding="2" align="center" border="0">
EOF
chunk ttag(), 'NO_TOP OUTPUT_MAP', <<EOF; # unless $opt->{no_top} or $wo;
<tr>
<td colspan="$span"$opt->{border_cell_extra}><img src="$opt->{clear_image}" width="1" height="$opt->{border_height}" alt="x"></td>
</tr>
EOF
if ($opt->{intro_text}) {
#::logDebug("intro_text=$opt->{intro_text}");
chunk ttag(), <<EOF;
<tr $opt->{spacer_row_extra}>
<td colspan="$span" $opt->{spacer_cell_extra}>$opt->{intro_text}</td>
</tr>
<tr $opt->{title_row_extra}>
<td colspan="$span" $opt->{title_cell_extra}>$::Scratch->{page_title}</td>
</tr>
EOF
}
$opt->{top_buttons_rows} = 5 unless defined $opt->{top_buttons_rows};
#### Extra buttons
my $extra_ok = $blob_widget
|| $opt->{output_map}
|| $linecount >= $opt->{top_buttons_rows}
|| defined $opt->{include_form}
|| $mlabel;
$mlabel ||= '&nbsp;';
if ($extra_ok and ! $opt->{no_top} and ! $opt->{nosave}) {
if($opt->{back_text}) {
chunk ttag(), 'OUTPUT_MAP', <<EOF; # unless $wo;
<tr$opt->{data_row_extra}>
<td$opt->{label_cell_extra}>&nbsp;</td>
<td align="left" colspan="$oddspan"$opt->{data_cell_extra}>
EOF
chunk 'COMBINED_BUTTONS_TOP', 'BOTTOM_BUTTONS OUTPUT_MAP', <<EOF;
<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>
<br>
EOF
chunk 'MLABEL', 'OUTPUT_MAP', 'MESSAGES', $mlabel;
chunk ttag(), <<EOF;
</td>
</tr>
$opt->{spacer_row}
EOF
}
elsif ($opt->{wizard}) {
chunk ttag(), 'NO_TOP OUTPUT_MAP', <<EOF;
<tr$opt->{data_row_extra}>
<td$opt->{label_cell_extra}>&nbsp;</td>
<td align="left" colspan="$oddspan"$opt->{data_cell_extra}>
EOF
chunk 'WIZARD_BUTTONS_TOP', 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF;
<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>
<br>
EOF
chunk 'MLABEL', 'NO_TOP OUTPUT_MAP', 'MESSAGES', $mlabel;
chunk ttag(), 'NO_TOP OUTPUT_MAP', <<EOF;
</td>
</tr>
$opt->{spacer_row}
EOF
}
else {
chunk ttag(), 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF;
<tr$opt->{data_row_extra}>
<td$opt->{label_cell_extra}>&nbsp;</td>
<td align="left" colspan="$oddspan"$opt->{data_cell_extra}>
EOF
chunk 'OK_TOP', 'NO_TOP OUTPUT_MAP', <<EOF;
<input type="submit" name="mv_click" value="$opt->{next_text}"$opt->{ok_button_extra}>
EOF
chunk 'CANCEL_TOP', 'NOCANCEL BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF;
&nbsp;
<input type="submit" name="mv_click" value="$opt->{cancel_text}"$opt->{cancel_button_extra}>
EOF
if($opt->{show_reset}) {
chunk 'RESET_TOP', 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF;
&nbsp;
<input type="reset"$opt->{reset_button_extra}>
EOF
}
chunk 'MLABEL', 'BOTTOM_BUTTONS OUTPUT_MAP', $mlabel;
chunk ttag(), 'BOTTOM_BUTTONS NO_TOP OUTPUT_MAP', <<EOF;
</td>
</tr>
$opt->{spacer_row}
EOF
}
}
chunk 'BLOB_WIDGET', $blob_widget; # unless $wo;
#### 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 $Vend::Cfg->{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{$_} || Vend::Data::database_exists_ref($_);
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} || Vend::Data::database_exists_ref($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;
chunk ttag(), <<EOF; # unless $wo;
<tr>
<td colspan="$span"$opt->{border_cell_extra}>
EOF
chunk 'CLONE_TABLES', <<EOF;
$tabform<input type="hidden" name="mv_check" value="clone_tables">
<input type="hidden" name="ui_clone_id" value="$opt->{ui_clone_id}">
EOF
chunk ttag(), <<EOF; # unless $wo;
</td>
</tr>
EOF
}
chunk_alias 'TOP_OF_FORM', qw/ FORM_BEGIN /;
chunk_alias 'TOP_BUTTONS', qw/
COMBINED_BUTTONS_TOP
WIZARD_BUTTONS_TOP
OK_TOP
CANCEL_TOP
RESET_TOP
/;
my %break;
my %break_label;
if($opt->{ui_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*/, $_, 2;
$break_label{$br} = $lab;
}
}
}
if(!$db and ! $opt->{notable}) {
return "<tr><td>Broken table '$table'</td></tr>";
}
my $passed_fields = $opt->{ui_data_fields};
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) {
#::logDebug("display_only: $_");
$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->{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;
if($opt->{left_width} and ! $opt->{label_cell_width}) {
$opt->{label_cell_extra} .= qq{ width="$opt->{left_width}"};
}
my $show_meta;
if($super and ! $opt->{no_meta}) {
$show_meta = defined $def->{ui_meta_force}
? $def->{ui_meta_force}
: $::Variable->{UI_META_LINK};
}
if($show_meta) {
if(! $opt->{row_template} and ! $opt->{simple_row}) {
$opt->{meta_prepend} = qq{<br$Trailer><font size="1">}
unless defined $opt->{meta_prepend};
$opt->{meta_append} = '</font>'
unless defined $opt->{meta_append};
}
else {
$opt->{meta_prepend} ||= '';
$opt->{meta_append} ||= '';
}
$opt->{meta_title} ||= errmsg('Edit field meta display info, table %s, column %s');
$opt->{meta_title_specific} ||= errmsg('Item-specific meta edit, table %s, column %s, key %s');
$opt->{meta_image_specific} ||= errmsg('specmeta.png');
$opt->{meta_image} ||= errmsg('meta.png');
$opt->{meta_image_extra} ||= 'border="0"';
$opt->{meta_anchor_specific} ||= errmsg('item-specific meta');
$opt->{meta_anchor} ||= errmsg('meta');
$opt->{meta_anchor_specific} ||= errmsg('item-specific meta');
$opt->{meta_extra} = " $opt->{meta_extra}"
if $opt->{meta_extra};
$opt->{meta_extra} ||= "";
$opt->{meta_extra} .= qq{ class="$opt->{meta_class}"}
if $opt->{meta_class};
$opt->{meta_extra} .= qq{ style="$opt->{meta_style}"}
if $opt->{meta_style};
}
my $row_template = convert_old_template($opt->{row_template});
#::logDebug("display_type='$opt->{display_type}' row_template length=" . length($row_template));
if(! $row_template) {
$opt->{display_type} = 'simple_row' if $opt->{simple_row};
$opt->{display_type} ||= 'image_meta' if $opt->{image_meta};
my $dt = $opt->{display_type} ||= 'default';
$dt =~ s/-/_/g;
$dt =~ s/\W+//g;
#::logDebug("display_type=$dt");
my $sub = $Display_type{$dt};
if(ref($sub) eq 'CODE') {
$row_template = $sub->($opt, $span);
}
else {
::logError("table-editor: display_type '%s' sub not found", $dt);
$row_template = $Display_type{default}->($opt, $span);
}
}
$row_template =~ s/~OPT:(\w+)~/$opt->{$1}/g;
$row_template =~ s/~([A-Z]+)_EXTRA~/$opt->{"\L$1\E_extra"} || $opt->{"\L$1\E_cell_extra"}/g;
$opt->{row_template} = $row_template;
$opt->{combo_template} ||= <<EOF;
<tr$opt->{combo_row_extra}><td> {LABEL} </td><td>{WIDGET}</td></tr>
EOF
$opt->{break_template} ||= <<EOF;
<tr$opt->{break_row_extra}><td colspan="$span" $opt->{break_cell_extra}\{FIRST?} style="$opt->{break_cell_first_style}"{/FIRST?}>{ROW}</td></tr>
EOF
my %serialize;
my %serial_data;
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;
}
}
}
my %link_row;
my %link_before;
if($opt->{link_table} and $key) {
#::logDebug("In link table routines...");
my @ltable;
my @lfields;
my @lkey;
my @lview;
my @llab;
my @ltpl;
my @lnb;
my @lrq;
my @lra;
my @lrb;
my @lba;
my @lbefore;
my @lsort;
my $tcount = 1;
if(ref($opt->{link_table}) eq 'ARRAY') {
@ltable = @{$opt->{link_table}};
@lfields = @{$opt->{link_fields}};
@lview = @{$opt->{link_view}};
@lkey = @{$opt->{link_key}};
@llab = @{$opt->{link_label}};
@ltpl = @{$opt->{link_template}};
@lnb = @{$opt->{link_no_blank}};
@lrq = @{$opt->{link_row_qual}};
@lra = @{$opt->{link_auto_number}};
@lrb = @{$opt->{link_rows_blank}};
@lba = @{$opt->{link_blank_auto}};
@lbefore = @{$opt->{link_before}};
@lsort = @{$opt->{link_sort}};
}
else {
@ltable = $opt->{link_table};
@lfields = $opt->{link_fields};
@lview = $opt->{link_view};
@lkey = $opt->{link_key};
@llab = $opt->{link_label};
@ltpl = $opt->{link_template};
@lnb = $opt->{link_no_blank};
@lrq = $opt->{link_row_qual};
@lra = $opt->{link_auto_number};
@lrb = $opt->{link_rows_blank};
@lba = $opt->{link_blank_auto};
@lbefore = $opt->{link_before};
@lsort = $opt->{link_sort};
}
while(my $lt = shift @ltable) {
my $lf = shift @lfields;
my $lv = shift @lview;
my $lk = shift @lkey;
my $ll = shift @llab;
my $lb = shift @lbefore;
my $lnb = shift @lnb;
my $ls = shift @lsort;
my $lrq = shift @lrq;
my $lra = shift @lra;
my $lrb = shift @lrb;
my $lba = shift @lba;
my $rcount = 0;
$ll ||= errmsg("Settings in table %s linked by %s", $lt, $lk);
my $whash = {};
my $ldb = database_exists_ref($lt)
or do {
logError("Bad table editor link table: %s", $lt);
next;
};
my $lmeta = $Tag->meta_record($lt, $lv);
$lf ||= $lmeta->{spread_fields};
my $l_pkey = $ldb->config('KEY');
$lrq ||= $l_pkey;
if($lba) {
my @f = grep /\w/, split /[\s,\0]+/, $lf;
@f = grep $_ ne $lk && $_ ne $l_pkey, @f;
$lf = join " ", @f;
}
my $an_piece = '';
if($lra) {
$an_piece = <<EOF;