HTML-EditableTable
view release on metacpan or search on metacpan
lib/HTML/EditableTable.pm view on Meta::CPAN
'catalog_id' => 'UX35AT',
'addition_date' => '2008-10-10',
'part_name' => 'control module',
'vendor' => 'Praxis',
'description' => 'ABS package with revA firmware. Used in low-cost applications and as replacement for model UX34AT. Includes adaptor wiring harness for UX34AT',
'qa_results' => 'see http://yoururl.com/index.cgi?context=qa',
'qoh' => '65',
'rohs_category' => 2,
'reorder_class' => 'C',
'last_order_date' => '2010-06-10',
},
{
'part_id' => 7961,
'catalog_id' => 'ZX42AT',
'addition_date' => '2009-03-01',
'part_name' => 'power regulator',
'vendor' => 'Armscor',
'description' => 'Minature power supply with redundant relays',
'qa_results' => '2ppm confirmed',
'qoh' => '32',
'rohs_category' => 2,
'reorder_class' => 'A',
'last_order_date' => '2009-12-17',
},
{
'part_id' => 8055,
'catalog_id' => 'UX24AT',
'addition_date' => '2007-04-08',
'part_name' => 'control module',
'vendor' => 'Subarashii',
'description' => 'Obsolete control module for A45 overthruster. Requires UX27AZ conditioner and 3F buffering caps if the overthruster runs >18psi',
'qa_results' => 'see http://yoururl.com/index.cgi?context=qa',
'qoh' => '2',
'rohs_category' => 4,
'reorder_class' => 'A',
'last_order_date' => '2005-08-19',
},
);
my @tableFields =
(
{
'editOnly' => 1,
'formElement' => 'deleteRowButton',
},
{
'dbfield' => 'part_id',
'label' => 'Part Id',
'viewOnly' => 1,
},
{
'dbfield' => 'catalog_id',
'label' => 'Catalog Id',
'formElement' => 'textfield',
'size' => 15,
'uniquifierField' => 'part_id',
},
{
'dbfield' => 'addition_date',
'label' => 'Available From',
'formElement' => 'calendar',
'uniquifierField' => 'part_id',
},
{
'dbfield' => 'part_name',
'label' => 'Part Name',
'formElement' => 'textfield',
'size' => 20,
'uniquifierField' => 'part_id',
},
{
'dbfield' => 'vendor',
'label' => 'Vendor',
'formElement' => 'popup',
'selectionList' => ['', 'Amexx', 'Armscor', 'Consolidated', 'Gentine', 'Oroco', 'Praxis', 'Shellalco', 'Subarashii',],
'uniquifierField' => 'part_id',
},
{
'dbfield' => 'description',
'label' => 'Part Description',
'formElement' => 'textarea',
'subBr' => 1,
'drillDownTruncate' => 60,
'uniquifierField' => 'part_id',
},
{
'dbfield' => 'qa_results',
'label' => 'QA Results',
'formElement' => 'textfield',
'linkifyContentOnView' => 1,
'uniquifierField' => 'part_id',
},
{
'dbfield' => 'qoh',
'label' => 'Quantity',
'formElement' => 'textfield',
'size' => 5,
'uniquifierField' => 'part_id',
},
{
'dbfield' => 'rohs_category',
'label' => 'RoHS',
'formElement' => 'popup',
'selectionList' => ['',1..10],
'selectionLabels' => {
1 => 'Large and small household appliances',
2 => 'IT equipment',
3 => 'Telecommunications equipment',
4 => 'Consumer equipment',
5 => 'Lighting equipment',
6 => 'Electronic and electrical tools',
7 => 'Toys, leisure, and sports equipment',
8 => 'Medical devices',
9 => 'Monitoring and control instruments',
10 => 'Automatic dispensers',
},
'uniquifierField' => 'part_id',
},
{
'dbfield' => 'reorder_class',
'label' => 'Reorder Class',
'formElement' => 'popup',
'selectionList' => ['', 'A', 'B', 'C'],
'uniquifierField' => 'part_id',
},
{
'dbfield' => 'last_order_date',
'label' => 'Last Ordered',
'formElement' => 'calendar',
'uniquifierField' => 'part_id',
},
);
######## CGI Controller ##########
my $t = CGI->new();
print $t->header();
my $context = $t->param('context') || 'view';
my $table = HTML::EditableTable::Horizontal->new
(
{
'tableFields' => \@tableFields,
'width' => '100%',
'jsAddData' => 1,
'editMode' => $context,
'data' => \@tableData,
'jsSortHeader' => 1,
}
);
print "<form method=post>";
$table->htmlDisplay();
my $nextContext = $context eq 'view' ? 'edit' : 'view';
print "<input type=submit name=context value=$nextContext>";
print "</form>";
=head1 DESCRIPTION
This module was developed to simplify the manipuation of complex tabular data in engineering and business-process web applications. The motivation was a rapid-prototype software development flow where the requirements gathering phase goes something ...
- toggling of the table between view and edit modes with support for common html widgets
- uniquification of form element data to support processing of html form submissions of table data
- support for rowspanning
- methods to generate javascript for dynamic addition and remove of rows and commonly needed features such as 'click-to-expand' text display, calendar widget date entry, and sorting
- support for callbacks when data need to be pre-processed prior to display
For the Horizontal table, data are provided to tables in an array-of-hashes (most common case) or a hash-of-hashes. For the Vertical table, a single hash of data produces a single column of data while a hash-of-hashes supports muliple data columns.
=head1 TABLE METHODS AND PARAMETERS
The class methods are designed along 'public' and 'private' lines. The intended use model is indicated in the method descriptions.
=cut
my $globalTableUid = 1;
# valid table parameters
my %validTableFieldKeys =
(
'dbfield' => 1,
'label' => 1,
'formElement' => 1,
'callback' => 1,
'selectionList' => 1,
'selectionLabels' => 1,
'subBr' => 1,
'subCommaForBr' => 1,
'style' => 1,
'editOnly' => 1,
'viewOnly' => 1,
'suppressCallbackOnEdit' => 1,
'align' => 1,
'width' => 1,
'size' => 1,
'minimalEditSize' => 1,
'maxLength' => 1,
'bgcolor' => 1,
'rowspanArrayKey' => 1,
'rowspanArrayUniquifier' => 1,
'rowspanArrayKeyForUniquification' => 1,
'masterCounterUniquify' => 1,
'styleHandler' => 1,
'default' => 1,
'modeModifier' => 1,
'tooltip' => 1,
'editOnlyOnNegativeValue' => 1,
'selectionListCallback' => 1,
'htmlSub' => 1,
'linkifyContentOnView' => 1,
'drillDownTruncate' => 1,
'uniquifierField' => 1,
'jsClearColumnOnEdit' => 1,
'checkBehavior' => 1,
);
=head2 new (public)
Common constructor for EditableTable-derived classes. Providing the required initialization data to new() can be done either by a hashref to table-level parameters or by calling the required set*() methods prior to rendering the table with htmlDispl...
my $table = EditableTable::Horizontal->new
(
{
'tableFields' => \@tableFields;
'data' => \@data,
'editMode' => 'edit'
}
);
or
my $table = EditableTable::Horizontal->new()
$table->setTableFields(\@tableFields);
$table->setData(\@data);
$table->setEditMode('edit');
=cut
sub new {
my $type = shift @_;
my $class = ref($type) || $type;
my $self = {};
#### public data which needs to be intitialized
$self->{'tableFields'} = [];
$self->{'data'} = undef;
$self->{'editMode'} = undef;
#### data which is intended to be private
# used for certain column options for name uniquification
$self->{'masterCounter'} = 1;
# default unique table id. Setting this is important if you have more than one table
$self->{'tableId'} = $globalTableUid++;
# used for uniquification of elements in embedded javascript
# since multiple tables may be in play, ensure initial value is unique
# note each table should embed its own javascript. Do this after 4.0 release
$self->{'elementUid'} = $self->{'tableId'} + int(rand(1000000)); # BFBA terrible hack
# flag to auto-display javascript if required
$self->{javascriptDisplayed} = 0;
# default directory for jsCaldendar when this feature is used
$self->{calendarDir} = 'jscalendar';
# table field parameter validation is on by default
$self->{validateTableFieldKeys} = 1;
# flag set when stdout is rerouted to avoid multiple acts of this
$self->{stdoutRerouted} = 0;
my $initData = shift @_;
bless $self, $class;
if ($initData) {
if (ref($initData) ne 'HASH') { confess "expecting hash reference to initialization data for $class, got a " . ref($initData); }
$self->initialize($initData);
}
return $self;
}
=head2 initialize (private)
Peforms validation of data provided to the constuctor and makes set*() calls.
=cut
sub initialize {
my $self = shift @_;
my $initData = shift @_;
if (exists $initData->{'data'}) { $self->setData(delete $initData->{'data'}); }
if (exists $initData->{'tableFields'}) { $self->setTableFields(delete $initData->{'tableFields'}); }
if (exists $initData->{'editMode'}) { $self->setEditMode(delete $initData->{'editMode'}); }
if (exists $initData->{'sortHeader'}) { $self->setSortHeader(delete $initData->{'sortHeader'}); }
if (exists $initData->{'jsSortHeader'}) { $self->setJsSortHeader(delete $initData->{'jsSortHeader'}); }
if (exists $initData->{'sortData'}) { $self->setSortData(delete $initData->{'sortData'}); }
if (exists $initData->{'tabindex'}) { $self->setTabindex(delete $initData->{'tabindex'}); }
if (exists $initData->{'title'}) { $self->setTitle(delete $initData->{'title'}); }
if (exists $initData->{'tableId'}) { $self->setTableId(delete $initData->{'tableId'}); }
if (exists $initData->{'width'}) { $self->setWidth(delete $initData->{'width'}); }
if (exists $initData->{'style'}) { $self->setStyle(delete $initData->{'style'}); }
if (exists $initData->{'jsAddData'}) { $self->setJsAddData(delete $initData->{'jsAddData'}); }
if (exists $initData->{'noheader'}) { $self->setNoheader(delete $initData->{'noheader'}); }
if (exists $initData->{'rowspannedEdit'}) { $self->setRowspannedEdit(delete $initData->{'rowspannedEdit'}); }
if (exists $initData->{'sortOrder'}) { $self->setSortOrder(delete $initData->{'sortOrder'}); }
if (exists $initData->{'border'}) { $self->setBorder(delete $initData->{'border'}); }
if (exists $initData->{'suppressUndefinedFields'}) {$self->setSuppressUndefinedFields(delete $initData->{'setSuppressUndefinedFields'}); }
if (exists $initData->{'calendarDir'}) {$self->setCalendarDir(delete $initData->{'calendarDir'}); }
if (exists $initData->{'validateTableFieldKeys'}) {$self->setValidateTableFieldKeys(delete $initData->{'validateTableFieldKeys'}); }
if (exists $initData->{'stringOutput'}) { $self->setStringOutput(delete $initData->{'stringOutput'}); }
my @remainingInitKeys = keys %$initData;
if (scalar(@remainingInitKeys)) {
confess "one or more table parameters are not understood (@remainingInitKeys)";
}
# further validation
# sorting options check - since there are three general ways to sort table data, ensure only one of them is used
if (exists($self->{sortOrder}) + exists($self->{sortHeader}) + exists($self->{jsSortHeader}) > 1) {
confess "Conflicting sort options have been specified. Only one of 'sortOrder', 'sortHeader', and 'jsSortHeader' may be specified";
}
}
=head2 setValidateTableFieldKeys (public)
Toggles validation of field-level parameters. Enabled by default. Disable only if validation is a performance issue.
$table->setValidateTableFieldKeys(0)
=cut
# validation of table field parameters
sub setValidateTableFieldKeys {
my $self = shift;
my $val = shift;
$self->{validateTableFieldKeys} = $self->checkBool($val);
}
=head2 isvalidTableFieldKey (private)
Called for each field-level key parameer if validateTableFieldKeys is enabled (the default).
=cut
sub isValidTableFieldKey {
my $self = shift;
my $key = shift;
if ($validTableFieldKeys{$key}) { return 1; }
else { return 0; }
}
=head2 getConfigParams (private)
this is used by the Javascript Object to determine which javascript code to write for the table
=cut
# this is used by the Javascript Object to determine which javascript code to write for the table
sub getConfigParams {
my $self = shift;
my @params = keys %$self;
return \@params
}
lib/HTML/EditableTable.pm view on Meta::CPAN
sub setNoHeader {
my $self = shift @_;
my $noheader = shift @_;
$self->{'noheader'} = $self->checkBool($noheader);
}
=head2 setRowspannedEdit (public)
By default, a rowspanned table will flatten in 'edit' mode, with the rowspanning column repeated for each of the spanned rows. This is done to enable editing of the relationship betwen the fields and to preserve unique ids. If this is not the desir...
$table->setRowspannedEdit(1);
=cut
sub setRowspannedEdit {
my $self = shift @_;
my $rowspannedEdit = shift @_;
$self->{'rowspannedEdit'} = $self->checkBool($rowspannedEdit);
}
=head2 setSortOrder (public);
For Horizontal tables with a hash of hashes data structure, this sorts the rows per the provided arrayref. For Vertical tables with multiple data columns, sorts the columns left-to-right per the provided arrayref.
$table->setSortOrder(['UX34IG' , 'UX45ZZ', 'RG01IG']);\
=cut
# horizontal tables provided with hash of hashes: an array ref to key list used to sort hash of hashes
sub setSortOrder {
my $self = shift @_;
my $sortOrder = shift @_;
if (ref($sortOrder ne 'ARRAY')) { confess "the sort order must be a reference to an array of the dataset keys which will used to determine the row order"; }
$self->{'sortOrder'} = $sortOrder;
}
=head2 setSuppressUndefinedFields (public)
For Vertical tables. Avoids displaying a row if the data value for that row is undefined. Useful for using a single key set with partial data.
$table->setSuppressUndefinedFields(1)
=cut
# block display of undefined fields - vertical table only
sub setSuppressUndefinedFields {
my $self = shift @_;
my $suppressUndefinedFields = shift @_;
$self->{'suppressUndefinedFields'} = $self->checkBool($suppressUndefinedFields);
}
=head2 getCalendarDir (public)
Returns the directory for installation of www.dynarch.com jscalendar-1.0, which is supported in the 'calendar' formElement. Defaults to 'jscalendar' if not set.
$self->getCalendarDir();
=cut
sub getCalendarDir {
my $self = shift @_;
return $self->{calendarDir};
}
=head2 setCalendarDir (public)
Directory for installation of www.dynarch.com jscalendar-1.0, which is supported in the 'calendar' formElement. Defaults to 'jscalendar' if not set.
$self->setCalendarDir('jscal_10');
=cut
sub setCalendarDir {
my $self = shift @_;
my $calendarDir = shift @_ || confess "missing calendar directory";
$self->{calendarDir} = $calendarDir;
}
=head2 htmlDisplay (public)
This method renders the html to STDOUT. If table requirements are not met, an exception will occur when this method is called.
$table->htmlDisplay()
=cut
sub htmlDisplay {
my $self = shift @_;
# re-route STDOUT to string if called for
my $stdout = undef;
if ($self->{stringOutput} && !$self->{stdoutRerouted}) {
open(TMPOUT, '>&', \*STDOUT) || confess "failed to save STDOUT";
close STDOUT;
open(STDOUT, '>', \$stdout) || confess "failed to reroute STDOUT to string";
# set flag nested calls don't redo this
$self->{stdoutRerouted} = 1;
}
# display javascript - this method will return immediately if the javascriptDispalyed flag is set.
$self->htmlJavascriptDisplay();
# ensure we have all the required parameters populated before attempting to create the table html
if (!$self->{'data'}) { confess "table parameters are incompelete - missing data!"; }
if (!$self->{'tableFields'}) { confess "table parameters are incompelete - missing table view specification!"; }
if (!$self->{'editMode'}) { confess "table parameters are incompelete - missing edit mode!"; }
# print a hidden template row if the jsAddData feature is being used
if ($self->{jsAddData}) {
$self->htmlAddDataSetup();
}
# sort the data server-side if this option is used
# many times this is the best approach, but sometimes the calling server code will have the data
# already sorted via SQL
if (CGI::param('orderByAsc') && $self->{sortData}) {
my @sortedTableData = sort {$a->{CGI::param('orderByAsc')} cmp $b->{CGI::param('orderByAsc')}} @{$self->{data}};
$self->{data} = \@sortedTableData;
}
elsif (CGI::param('orderByDesc') && $self->{sortData}) {
my @sortedTableData = sort {$b->{CGI::param('orderByDesc')} cmp $a->{CGI::param('orderByDesc')}} @{$self->{data}};
$self->{data} = \@sortedTableData;
}
# virtual method which must be inherited for core table drawing
lib/HTML/EditableTable.pm view on Meta::CPAN
print "$html";
}
elsif (!exists $colSpec->{'formElement'}) {
print "<input type=text disabled>";
}
my $formElm = $colSpec->{'formElement'};
my $name = $colSpec->{'dbfield'};
my $id = $colSpec->{'dbfield'};
if (exists $colSpec->{'rowspanArrayKey'}) {
$name = $colSpec->{'rowspanArrayKey'};
$id = $colSpec->{'rowspanArrayKey'};
}
if (defined $formElm) {
if ($formElm eq 'textfield') {
print "<input type=textfield name='$name' id='$id'>";
}
elsif ($formElm eq 'textarea') {
print "<textarea rows=3 cols=60 name='$name' id='$id'></textarea>";
}
elsif ($formElm eq 'popup') {
if (exists $colSpec->{'selectionLabels'}) {
print popup_menu(-id=>$name, -name=>$name, -id=>$id, -values=>$colSpec->{'selectionList'}, -labels=>$colSpec->{'selectionLabels'}, -override=>1);
}
else {
print popup_menu(-id=>$name, -name=>$name, -id=>$id, -values=>$colSpec->{'selectionList'}, -override=>1);
}
}
elsif ($formElm eq 'scrollingList') {
my $selectionCount = scalar(@{$colSpec->{'selectionList'}});
if ($selectionCount > 8) { $selectionCount = 8; }
if (exists $colSpec->{'selectionLabels'}) {
print scrolling_list(-name=>$name, -id=>$id, -values=>$colSpec->{'selectionList'}, -labels=>$colSpec->{'selectionLabels'}, -override=>1, -size=>$selectionCount, -multiple=>'true');
}
else {
print scrolling_list(-name=>$name, -id=>$id, -values=>$colSpec->{'selectionList'}, -size=>$selectionCount, -multiple=>'true', -override=>1);
}
}
elsif ($formElm eq 'deleteRowButton') {
print "<input name='remove' id=remove type='button' value='remove' onClick=\"this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode);\">";
}
elsif($colSpec->{'formElement'} eq 'html5Calendar') {
print "<input id='$id' name='$name' type=date/>";
}
elsif($colSpec->{'formElement'} eq 'checkbox') {
my $checked = '';
if ($colSpec->{'checkBehavior'} eq 'checked' ) { $checked = 'checked' }
print "<input type=checkbox name='$name' id='$id' $checked/>";
}
# dynarch jscalendar-1.0 support
elsif($colSpec->{'formElement'} eq 'calendar') {
# script credit to www.dynarch.com
my $spanText = 'Click here to add date';
print "<div id=jscalsetup_$self->{tableId}>";
print "<input type=\"hidden\" name=\"$name\" id=\"$id\"/>\n";
print "<span style=\"background-color: \#fff; cursor: default;\"\n";
print "onmouseover=\"this.style.backgroundColor='#eee';\"\n";
print "onmouseout=\"this.style.backgroundColor='#fff';\"\n";
print "id=\"showD_$name\"\n";
print "><b>$spanText</b></span>";
print "</div>";
}
}
}
print "</div>";
}
##########################################################################
# These methods are intended to be protected
##########################################################################
=head2 makeTable (abstract protected)
Method which must be provided by a class dervied from EditableTable.
=cut
sub makeTable {
my $self = shift @_;
confess "cannot use the base class here - must use a derived class";
}
##########################################################################
# These methods are intended to be private
##########################################################################
# used for the horizontal table case and vertical table case
=head2 getTableTagAttributes (private)
processes and returns a string of attributes for the top-level <table> tag
=cut
sub getTableTagAttributes {
my $self = shift @_;
my @tableAttributes = ();
if (exists $self->{'border'}) {
push @tableAttributes, "border='" . $self->{'border'} . "'";
}
else {
lib/HTML/EditableTable.pm view on Meta::CPAN
my $size;
if (exists $colSpec->{'size'}) {
$size = $colSpec->{'size'};
}
elsif (exists $colSpec->{'minimalEditSize'}) {
$size = length($default) * 1.2;
if ($size < 10) { $size = 15; }
}
else {
$size = length($default) * 2;
if ($size < 60) { $size = 40; }
}
my $maxlength = 255; # danger really need to check db metadata.
if ($colSpec->{'maxlength'}) { $maxlength = $colSpec->{'maxlength'}; }
print "<td $tdFormat>" . textfield(-tabindex=>$tabindex, -id=>$name, -name=>$name, -value=>"$default", -size=>$size, -maxlength=>$maxlength, -override=>1) . "</td>";
}
elsif ($colSpec->{'formElement'} eq 'textarea') {
my $default = $cellValue;
my $size = length($default) * 2;
my $cols = 80;
if ($colSpec->{minimalEditSize}) { $cols = 40; }
my $rows = sprintf("%i", $size/80);
if ($rows < 4) { $rows = 4; }
print "<td $tdFormat>" . textarea(-tabindex=>$tabindex, -id=>$name, -name=>$name, -cols=>$cols, -rows=>$rows, -value=>$default, -override=>1) . "</td>";
}
elsif ($colSpec->{'formElement'} eq 'checkbox') {
my $default = $cellValue;
my $checked = '';
if ($colSpec->{checkBehavior} eq 'checkedOnVal') {
if ($default) { $checked = 'checked' }
}
elsif($colSpec->{checkBehavior} eq 'checkedOnTrue') {
if ($default =~ /^1|yes|true$/i) { $checked = 'checked' }
}
elsif ($colSpec->{checkBehavior} eq 'checked') {
$checked = 'checked';
}
elsif($colSpec->{checkBehavior}) {
confess ("$colSpec->{checkBehavior} is not a valid value (checkedOnVal, checkedOnTrue, checked)");
}
if ($default) {
print "<td $tdFormat>" . "<input type=checkbox name=\"$name\" id=\"$name\" value=\"$default\" $checked>" . "</td>";
}
else {
print "<td $tdFormat>" . "<input type=checkbox name=\"$name\" id=\"$name\" value=\"$name\" $checked>" . "</td>";
}
}
elsif($colSpec->{'formElement'} eq 'hidden') {
print "<td $tdFormat>";
print hidden(-id=>$name, -name=>$name, -id=>$name, -value=>$cellValue);
print $cellValue;
print "</td>";
}
# depends on the calendar being loaded in the header
elsif($colSpec->{'formElement'} eq 'html5Calendar') {
print "<td $tdFormat>";
print "<input id=\"$name\" name=\"$name\" type=date value=\"$cellValue\"/>";
print "</td>";
}
# dynarch jscalendar-1.0 support
elsif($colSpec->{'formElement'} eq 'calendar') {
# script credit to www.dynarch.com
my $spanText;
if ($cellValue) {
$spanText = $cellValue;
}
else {
$cellValue = '';
$spanText = 'Click here to add date';
}
print "<td $tdFormat>";
print "<input type=\"hidden\" name=\"$name\" id=\"$name\" value=\"$cellValue\"/>\n";
print "<span style=\"background-color: \#fff; cursor: default;\"\n";
print "onmouseover=\"this.style.backgroundColor='#eee';\"\n";
print "onmouseout=\"this.style.backgroundColor='#fff';\"\n";
print "id=\"showD_$name\"\n";
print "><b>$spanText</b></span>\n";
print "<script type=\"text/javascript\">\n";
print "Calendar.setup({\n";
print "inputField : \"$name\",\n";
print "ifFormat : \"%Y-%m-%d\",\n";
print "displayArea : \"showD_$name\",\n";
print "daFormat : \"%Y-%m-%d\",\n";
print "cache : true\n";
print "});\n";
print "</script>\n";
print "</td>";
}
elsif ($colSpec->{'formElement'} eq 'deleteRowButton') {
print "<td $tdFormat>";
print "<input name='remove' id=remove type='button' value='remove' onClick=\"this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode);\">";
print "</td>";
}
else {
confess join ":", "Unknown form element ($colSpec->{'formElement'})!", __FILE__, __LINE__;
}
}
elsif (exists $colSpec->{'htmlSub'}) {
my $html = $colSpec->{'htmlSub'};
$html =~ s/<$colSpec->{'dbfield'}>/$cellValue/g;
print "<td $tdFormat>" . $html . "</td>";
}
else {
my $name;
# if we have a list and labels for a popup, assume the provided value is an index to the label;
if (exists $colSpec->{'selectionList'} && exists $colSpec->{'selectionLabels'}) {
print "<td $tdFormat>" . $colSpec->{'selectionLabels'}->{$cellValue} . "</td>";
}
else {
lib/HTML/EditableTable.pm view on Meta::CPAN
$link =~ s/\?/\\\?/g;
$content =~ s/($link)/\<a href=\"$1\"\>$1\<\/a\>/;
}
}
# sometimes we want to substitue <br> for newlines
if ($colSpec->{'subBr'}) {
$content =~ s/\n/\<br\>/g;
}
# some large text displays are truncated or provided with expandable links
if ($colSpec->{'drillDownTruncate'} && length($content) > $colSpec->{'drillDownTruncate'}) {
(my $truncatedContent) = $content =~ /(.{0,$colSpec->{'drillDownTruncate'}})/;
$truncatedContent .= "...<input type=button style=\"font-size:65%;\" value=more>";
my $tdUid = $self->{'elementUid'}++;
my $divUid = $self->{'elementUid'}++;
my $truncatedDivUid = $self->{'elementUid'}++;
my $aUid = $self->{'elementUid'}++;
print "<td $tdFormat id=$tdUid>" . "<div style=text-decoration:none id=$aUid href=\"javascript:void(0)\" onclick=\"expandText($aUid, $divUid, $truncatedDivUid);\">" . $truncatedContent . "</div>" . "<div id=$divUid style=display:none>$content</div...
}
else {
if (!defined($content)) { $content = ''; }
print "<td $tdFormat>" . $content . "</td>";
}
}
}
}
}
=head1 TABLE FIELD PARAMETERS
=head2 dbfield (frequent)
Specifies the data hash key for provided data for the table element. Also Specifies the form element base name that will be used. Typically, this is also a database field name.
'dbfield' => part_id
=head2 label (frequent)
Specifies the table column header for horizontal tables and the first colum for vertical tables.
'label' => 'Part Id#'
=head2 formElement (frequent)
formElement specifies the html input field that will be used when the table is in 'edit' mode. Valid values are
=over
=item *
calendar - Implements popup calendar using www.dynarch.com jscalendar 1.0. Requires this javascript library to be accessible. See L</"JAVASCRIPT INTEGRATION"> for details.
=item *
checkbox - Implements checkbox html element
=item *
deleteRowButton - Combine with jsAddRow Table-level feature. Provides button to delete a table row.
=item *
html5Calendar - Alternative to calendar. Implements HTML5 'date' input type. Tested with Opera, which is the only browser supporting this HTML5 input as of this writing.
=item *
hidden - Implements hidden element type.
=item *
popup - implements CGI "poup"
=item *
scrollingList - implments CGI "scrolling_list"
=item *
textarea - implements textarea HTML element
=item *
textfield - implements html input of type 'text'
=back
'formElement' => 'checkbox'
=head2 selectionList (frequent)
An array reference to a list of values to display in a popup or scrollingList
'selectionList' => [ '34GXT', '35TTG', '56YUG' ]
=head2 selectionLabels (frequent)
A hash reference to labels and values to present in a popup or scrollingList. The keys are displayed while the values are provided in the form.
'selectionLabels' => { '34GXT' => 'Big Widget', '35TTG' => 'Medium Widget', '56YUG' => 'Small Widget' }
=head2 editOnly (frequent)
Don't display this field when the table is in 'view' mode.
'editOnly' => 1
=head2 viewOnly (frequent)
Don't display this field when the table is in 'edit' mode.
'viewOnly' => 1
=head2 default (frequent)
Provides default value for form elements when no value is present in the data
'default' => 'UX56SG'
=head2 uniquifierField (frequent)
Sets the field whose 'dbfield' value is used to provide a unique name and id to other form elements in the same array (row or column, depending on the implementation) of data. This is done by appending an '_' and then the value of the specified dbfi...
my @tableFields = (
lib/HTML/EditableTable.pm view on Meta::CPAN
$table->setJsSortHeader(1);
=head2 Click-to-Expand Text
This feature truncates text to the specified number of characters by default and provides a "more" button to display the full text. A click on the full text returns the cell to its truncated state. This feature is useful when the full text consumes...
my $tableField =
{
'dbfield' => 'description',
'label' => 'Part Description',
'drillDownTruncate' => 60,
};
=head2 Mousover Tooltips
The mouseover tooltip feature is provided using javascript from David Flanagan's Javascript, The Definitive Guide. To use this feature set the table field 'tooltip' parameter:
my $tableField =
{
'dbfield' => 'description',
'label' => 'Part Description',
'tooltip' => 'Description to be displayed in catalog.'
};
=head2 Add and Delete Table Rows
Client-side row addition and deletion is provided by javascript. When this feature is used, a <div> tag with a row template is written when htmlDisplay() is called. For row additions, sequential negative integers are used to provide unique identifi...
@tableFields = (
{
'dbfield' => 'part_id',
'label' => 'Part ID#'
'formElement' => 'textField'
},
{
'dbfield' => 'sub_data',
'label' => 'Part Sub-Assembly',
'formElement' => 'textField'
}
);
The first dynamically added row will have input field ids as follows:
<input id="part_id_-1" type="textfield"/>
<input id="sub_data_-1" type="textfield"/>
To use this feature, set the table-level parameter 'jsAddData':
$table->jsAddData(1);
To provide a means to delete an added row, specify a table field with the following parameters:
my $tableField = {
'editOnly' => 1,
'formElement' => 'deleteRowButton'
}
=head2 Calendar Input Widget
Date entry is very common in the target applications of EditableTable. A formElement of type 'calendar' is provided by impementing dynarch.com's jscalendar-1.0 widget. The jscalendar library must be available to the webserver. The default director...
Calendar.setup(
inputField : $name,
ifFormat : "%Y-%m-%d",
displayArea : "showD_$name",
daFormat : "%Y-%m-%d",
cache : true
)
To provide a jscalendar input, use the formElement type 'calendar',
my $tablField = {
'dbfield' => 'addition_date',
'label' => 'Available From',
'formElement' => 'calendar',
},
Note also that html5 provides a (much appreciated) date entry input type. This is supported by using the formElement type 'html5calendar'. As of this writing only Opera supported this html5 feature, however, so the jscalendar integration will be us...
=head1 SORTING
There are four techniques available to sort HTML::EditableTable::Horizontal tables and one for multi-column HTML::EditableTable::Vertical. Each is described below along with examples.
=head2 Server-side by user
Typically used when the data for the table are presorted by SQL or in pre-processing. In this case, set the sortHeader url, which will call the cgi script with the appropriate column field value. The user is responsible for interpreting the cgi par...
EditableTable appends 'orderByAsc=<dbfield>' or 'orderByDesc=<dbfield>' to the url set in the call to sortHeader(), so the url must be constructed by the user in anticipation of this. When the table is first displayed, the parameter 'orderByAsc' is ...
$table->sortHeader("http://myscript.cgi?context=edit&");
An examle url attached to each column header by EditableTable is "http://myscript.cgi?context=edit&sortby=partId".
a simplified example of the server cgi code implementing the sort:
my $t = CGI->new();
my $sortby = $t->param('orderByAsc');
my $sql = "select * from table order by $sortby";
=head2 Server-side by EditableTable
Reversible data sorting is handled by the EditableTable object. The user only needs to provide a value for sortHeader and set sortData to 1 - no additional server-side work is needed. This technique will sort rowspanned tables.
$table->sortHeader("http://myscript.cgi?context=edit&");
$table->sortData(1);
=head2 Server-side by setting sortKeys
This technique is appropriate for hash-of-hashes data structures in both Horizontal and multi-column Vertical tables. In this case, the user provides an arrayref to the sorted keys. Horizontal tables are sorted top-to-bottom in array order and mul...
$table->setSortOrder( ['UX45TG', 'HU78OO', 'UV01TT'] );
=head2 Client-side by javascript
This technique implements client-side javascript to provide reversible sorting without calling the cgi. It does not currently work with rowspanning tables. The user need only set jsSortHeader to enable this functionality.
$table->setJsSortHeader(1);
=head1 OTHER EXAMPLES
There are several examples provided in the 'example' directory in the module distribution. These examples also comprise the test case suite for this module.
=head1 AUTHOR
This code is provided courtesy of Freescale Semicondutor. The developers are
Andy Espenscheid C<< <espenshovel@gmail.com> >>
Sergei Kondratiev
Vishesh Kumar
Vijay Yadav
Mike Boatright
=head1 BUGS & POTENTIAL ISSUES
( run in 0.683 second using v1.01-cache-2.11-cpan-39bf76dae61 )