HTML-EditableTable

 view release on metacpan or  search on metacpan

lib/HTML/EditableTable.pm  view on Meta::CPAN

       '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.

lib/HTML/EditableTable.pm  view on Meta::CPAN

  if ($val !~ /^1|true|0|false$/) { confess "value ($val) is a flag which must be set to 0, 1, 'true', or 'false'"; }
  if ($val eq 'false') { $val = 0; }
  elsif ($val eq 'true') { $val = 1; }
  
  return $val;
}
    
=head2 htmlAddDataSetup (private)

Used outside the table to create a <div> for the insertion of new table rows with javascript

=cut

sub htmlAddDataSetup {

  my $self = shift @_;
  my $tableFields = $self->{'tableFields'} || confess "missing table fields";
  
  my $tableId = $self->getTableId();
  
  print "<div name=readroot_$tableId id=readroot_$tableId style=\"display:none\">";

  foreach my $colSpec (@$tableFields) {

    next if (exists $colSpec->{'viewOnly'});

    # limited callback support

    if (exists $colSpec->{'callback'} && !$colSpec->{'dbfield'}) {

      my $fp = $colSpec->{'callback'};

      if (!$fp) { confess "no callback exists for $colSpec->{label}!"; }
      my $html = &$fp(undef, $colSpec);
      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

lib/HTML/EditableTable.pm  view on Meta::CPAN

      # reset the flag
      $self->{'vtableFirstColumn'} = undef;
    }

    # we must have a cell value defined at this point

    if ($editMode eq 'edit' && exists $colSpec->{'formElement'}) {

      # sometimes we swap form elements if there is no value
      if (!$cellValue && exists ($colSpec->{formElementOnNull})) { $colSpec->{formElement} = $colSpec->{formElementOnNull}; }

      my $name = $colSpec->{'dbfield'};

      if (exists $colSpec->{'rowspanArrayKey'}) {
	$name = $colSpec->{'rowspanArrayKey'}
      }

      if (exists $colSpec->{'uniquifierField'}) {

	# maybe an array of unquifiers

	if (ref($colSpec->{'uniquifierField'})) {
	  foreach my $uniquifier (@{$colSpec->{'uniquifierField'}}) {
	    if (!exists $row->{$uniquifier}) { confess join ":", "field '$uniquifier' called for as a uniquifier but no value exists", __FILE__, __LINE__; }
	    $name .= '_' . $row->{$uniquifier};
	  }
	}
	else {
	  if (!exists $row->{$colSpec->{'uniquifierField'}}) { confess join ":", "field '$colSpec->{'uniquifierField'}' called for as a uniquifier but no value exists", __FILE__, __LINE__; }
	  $name .= '_' . $row->{$colSpec->{'uniquifierField'}};
	}

	# S.K.: Improvement
	if (ref($colSpec->{'rowspanArrayUniquifier'})) {
	  foreach my $rowspan_uniquifier (@{$colSpec->{'rowspanArrayUniquifier'}}) {
	    if (exists $colSpec->{'rowspanArrayKeyForUniquification'}) {
	      $name .= '_' . $row->{$colSpec->{'rowspanArrayKeyForUniquification'}}->[$rowspanSubcounter]->{$rowspan_uniquifier};
	    }
	    elsif (exists $colSpec->{'dbfield'}) {
	      $name .= '_' . $row->{$colSpec->{'dbfield'}}->[$rowspanSubcounter]->{$rowspan_uniquifier};
	    }
	    else {
	      confess join ":", "field '$rowspan_uniquifier' called for as a uniquifier but no value exists", __FILE__, __LINE__;
	    }
	  }
	}
	else {
	  if (exists $colSpec->{'rowspanArrayUniquifier'} && exists $colSpec->{'rowspanArrayKeyForUniquification'}) {
	    $name .= '_' . $row->{$colSpec->{'rowspanArrayKeyForUniquification'}}->[$rowspanSubcounter]->{$colSpec->{'rowspanArrayUniquifier'}};
	  }
	  elsif (exists $colSpec->{'rowspanArrayUniquifier'}) {
	    $name .= '_' . $row->{$colSpec->{'dbfield'}}->[$rowspanSubcounter]->{$colSpec->{'rowspanArrayUniquifier'}};
	  }
	}
      }

      if (exists $colSpec->{'masterCounterUniquify'}) {
	$name .= '_' . $self->{'masterCounter'}++;
      }

      if ($colSpec->{'formElement'} eq 'popup') {

	# we typically use a negative id for new data - sometimes we need to prevent edits on existing rows

	my $disabled = undef;

	if (exists $colSpec->{'editOnlyOnNegativeValue'} && $cellValue > 0) {
	  $disabled = 'DISABLED';
	}

	if (exists $colSpec->{'selectionListCallback'}) {
	  my $fp = $colSpec->{'selectionListCallback'};
	  my $selectionList = &$fp($row);
	  print "<td $tdFormat>" . popup_menu(-tabindex=>$tabindex, -id=>$name, -name=>$name, -values=>$selectionList, -default=>$cellValue, -override=>1) . "</td>";
	}
	elsif (exists $colSpec->{'selectionLabels'}) {
	  print "<td $tdFormat>";

	  # new version of firefox maybe? - undef disabled flag now results in disabled field??
	  if ($disabled) {
	    print popup_menu(-tabindex=>$tabindex, -disabled=>$disabled, -id=>$name, -name=>$name, -values=>$colSpec->{'selectionList'}, -labels=>$colSpec->{'selectionLabels'}, -default=>$cellValue, -override=>1);
	  }
	  else {
	    print popup_menu(-tabindex=>$tabindex, -id=>$name, -name=>$name, -values=>$colSpec->{'selectionList'}, -labels=>$colSpec->{'selectionLabels'}, -default=>$cellValue, -override=>1);
	  }
	  # disabled field will not submit

	  if ($disabled) {
	    print "<input type=hidden name=\"$name\" value=\"$cellValue\">";
	  }
	  print "</td>";
	}
	else {
	  print "<td $tdFormat>" . popup_menu(-tabindex=>$tabindex, -id=>$name, -name=>$name, -values=>$colSpec->{'selectionList'}, -default=>$cellValue, -override=>1) . "</td>";
	}
      }
      elsif ($colSpec->{'formElement'} eq 'scrollingList') {

	my $selectionCount = scalar(@{$colSpec->{'selectionList'}});
	if ($selectionCount > 8) { $selectionCount = 8; }

	# split apart a comma delmited list
	my @cellValues = split ',', $cellValue;

	if (exists $colSpec->{'selectionLabels'}) {

	  print "<td $tdFormat>", scrolling_list(-tabindex=>$tabindex, -id=>$name, -name=>$name, -values=>$colSpec->{'selectionList'}, -labels=>$colSpec->{'selectionLabels'}, -override=>1, -size=>$selectionCount, -multiple=>'true', -default=>[@cellValues]),...
	}
	else {
	  print "<td $tdFormat>", scrolling_list(-tabindex=>$tabindex, -id=>$name, -name=>$name, -values=>$colSpec->{'selectionList'}, -size=>$selectionCount, -multiple=>'true', -override=>1, -default=>[@cellValues]), "</td>";
	}
      }
      elsif ($colSpec->{'formElement'} eq 'textfield') {

	my $default = $cellValue || '';

	if ($colSpec->{'subBr'}) {
	  $default =~ s/\n/\<br\>/g;
	}
	elsif ($colSpec->{'subCommaForBr'}) {
	  $default =~ s/\<br\>/,/g;
	}

	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 = '';
	

lib/HTML/EditableTable.pm  view on Meta::CPAN

	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 {

	# sometimes we want to substitue <br> for newlines

	my $content = $cellValue;

	if ($colSpec->{'subBr'}) {
	  $content =~ s/\n/\<br\>/g;
	}

	# if the content is short is short enough an contains thinks that looks like links, convert them to links

	if ($colSpec->{'linkifyContentOnView'}) {

	  (my @links) = $content =~ /(http\:\/\/\S+)/g;
	  foreach my $link (@links) { 
	    
	    # escape $link
	    $link =~ s/\//\\\//g;
	    $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 = (
 {
  'dbfield' => 'part_id'
 }
 {
  'dbfield' => 'description',  
  'uniquifierField' => 'part_id'
  'formElement' => 'textfield',  
 }
 );

with data
  
 my @data = (
 'part_id' => 1234
  )

will produce
 
 <input type=text id='description_1234' name='description_1234'> 

This field can also be an arrayref to a list of fields.  The values of each will be joined with '_' characters to create the field name and id.

=head2 drillDownTruncate (occasional)

Implements a javascript which truncates the text and provides a toggle to switch between full and truncated text.  The value sets the number of characters which are displayed in 'truncated' mode.

 'drillDownTruncate' => 100

=head2 callback (occasional)

Provides a mechanism to process data with a custom function or method prior to display.  Typically used for custom formatting, setting table values based on other values, or 'child' database queries.

 'callback' => &callbackFunction

lib/HTML/EditableTable.pm  view on Meta::CPAN

    'dbfield' => 'part_id',
    'label' => 'Part ID#'
   },
   {
    'dbfield' => 'sub_data',
    'rowspanArrayKey' => 'sub_assembly_name',
    'label' => 'Part Sub-Assembly',
    'masterCounterUniquify' => 1
   }
 );

 produces a table with the following unique id's in 'edit' mode.  Note the flattening of the table in 'edit' mode.  If this is not desired set the table-level param 'rowspannedEdit'.

 ------------------------------------------------------
 | Part ID# |                Part Sub-Assembly        |
 |----------|-----------------------------------------|
 | UX34GT   |        34R (sub_assembly_name_1)        |
 |----------|-----------------------------------------|
 | UX34GT   |        23D (sub_assembly_name_2)        |
 |----------|-----------------------------------------|
 | RT67IV   |        14G (sub_assembly_name_3)        |
 |----------|-----------------------------------------|
 | RT67IV   |        13R (sub_assembly_name_4)        |
 ------------------------------------------------------

=head2 styleHandler (rare)

Callback mechanism to provide a style for the <td> tag.

 'styleHandler' => &getStyle

The interface for the callback is

 &getStyle(
   $row # hashref to the current data set 
 ) 

=head2 modeModifier (rare)

Provides a callback mechanism to determine if the mode ('view' or 'edit') should be changed for this field only

 'modeModifer' => &getMode

The interface for this callback is as follows:

 &getMode (
    $editMode, # 'view' or 'edit'
    $data # hashref to the current dataset (row or column, depending on table type)
 )

The callback must return 'view' or 'edit'.

=head2 editOnlyOnNegativeValue (rare)
 
Use this feature when it is desired to prevent editing on existing data and only allow edting of new data where the value is <0

 'editOnlyOnNegativeValue' => 1,

=head2 selectionListCallback (rare)

Specifies a callback to produce a value list for a popup or scrollingList element.  Useful when a dynamic query or calculation must be made.

 'selectionListCallback' => &getListValues

The interface for this call back is as follows:

 &getListValues (
   $dataSet # current row or column data hashref
 );

The callback must provide an arrayref of values. 

=head2 htmlSub (rare)

provides a simple mechanism to substitute the current field's value into a string template

 'htmlSub' => "http://www.site.com?value=<dbfield>"

The '<dbfield>' text will be replaced by the current dataset's $data->{dbfield}

=head1 JAVASCRIPT INTEGRATION

=head2 Overview

The javascript features are encapsulated in HTML::EditableTable::Javascript.  Each method provided by this class provides a distinct javascript feature, allowing for clean override or extension. The intent is to provide all javascript functionality "...

To use a custom javascript class, call setJavascript() prior to htmlDisplay() or  htmlJavascriptDisplay().  Note that the Javascript object requires a reference to the parent table so it must be created after the table.

Inherit from the HTML::EditableTable::Javascript class to extend or override javascript code generation.
                                                                                                                                                                   
 package MyNewJavascript {                                                                                                                                          
 @ISA = qw(HTML::EditableTable::Javascript);                                                                                                                        
   # new code
 }
 1;                                                                                                                                                                 

 my $table = HTML::EditableTable::Vertical->new(); 

 my $customJavascript = MyNewJavascript->new($table);                                                                                                               
 $table->setJavascript($customJavascript);                                                                                                                          

The functionality provided through javascript are described below:

=head2 Table Sorting

Client-side table sorting is implemented using Stuart Langridge's SortTable Version 2: http://www.kryogenix.org/code/browser/sorttable/  See L</"SORTING"> for more details on this and other table sorting options.  To use this feature set the 'jsSortH...

 $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



( run in 1.589 second using v1.01-cache-2.11-cpan-364913b4093 )