File Coverage

blib/lib/HTML/EditableTable.pm
Criterion Covered Total %
statement 15 589 2.5
branch 0 350 0.0
condition 0 115 0.0
subroutine 5 47 10.6
pod 41 42 97.6
total 61 1143 5.3


line stmt bran cond sub pod time code
1             package HTML::EditableTable;
2              
3 1     1   60304 use warnings;
  1         4  
  1         47  
4 1     1   7 use strict;
  1         2  
  1         45  
5 1     1   10 use Carp qw(confess);
  1         16  
  1         70  
6 1     1   10624 use CGI qw(:standard);
  1         34415  
  1         8  
7              
8 1     1   5184 use HTML::EditableTable::Javascript;
  1         3  
  1         9740  
9              
10             =head1 NAME
11              
12             HTML::EditableTable - Classes for html presentation of tabular data with view and edit modes.
13              
14             =head1 VERSION
15              
16             Version 0.21
17              
18             =cut
19              
20             our $VERSION = '0.21';
21              
22             =head1 SYNOPSIS
23              
24             use HTML::EditableTable;
25             use HTML::EditableTable::Horizontal;
26              
27             my @tableData =
28             (
29             {
30             'part_id' => 7765,
31             'catalog_id' => 'UX35AT',
32             'addition_date' => '2008-10-10',
33             'part_name' => 'control module',
34             'vendor' => 'Praxis',
35             'description' => 'ABS package with revA firmware. Used in low-cost applications and as replacement for model UX34AT. Includes adaptor wiring harness for UX34AT',
36             'qa_results' => 'see http://yoururl.com/index.cgi?context=qa',
37             'qoh' => '65',
38             'rohs_category' => 2,
39             'reorder_class' => 'C',
40             'last_order_date' => '2010-06-10',
41             },
42             {
43             'part_id' => 7961,
44             'catalog_id' => 'ZX42AT',
45             'addition_date' => '2009-03-01',
46             'part_name' => 'power regulator',
47             'vendor' => 'Armscor',
48             'description' => 'Minature power supply with redundant relays',
49             'qa_results' => '2ppm confirmed',
50             'qoh' => '32',
51             'rohs_category' => 2,
52             'reorder_class' => 'A',
53             'last_order_date' => '2009-12-17',
54             },
55             {
56             'part_id' => 8055,
57             'catalog_id' => 'UX24AT',
58             'addition_date' => '2007-04-08',
59             'part_name' => 'control module',
60             'vendor' => 'Subarashii',
61             'description' => 'Obsolete control module for A45 overthruster. Requires UX27AZ conditioner and 3F buffering caps if the overthruster runs >18psi',
62             'qa_results' => 'see http://yoururl.com/index.cgi?context=qa',
63             'qoh' => '2',
64             'rohs_category' => 4,
65             'reorder_class' => 'A',
66             'last_order_date' => '2005-08-19',
67             },
68             );
69              
70             my @tableFields =
71             (
72             {
73             'editOnly' => 1,
74             'formElement' => 'deleteRowButton',
75             },
76             {
77             'dbfield' => 'part_id',
78             'label' => 'Part Id',
79             'viewOnly' => 1,
80             },
81             {
82             'dbfield' => 'catalog_id',
83             'label' => 'Catalog Id',
84             'formElement' => 'textfield',
85             'size' => 15,
86             'uniquifierField' => 'part_id',
87             },
88             {
89             'dbfield' => 'addition_date',
90             'label' => 'Available From',
91             'formElement' => 'calendar',
92             'uniquifierField' => 'part_id',
93             },
94             {
95             'dbfield' => 'part_name',
96             'label' => 'Part Name',
97             'formElement' => 'textfield',
98             'size' => 20,
99             'uniquifierField' => 'part_id',
100             },
101             {
102             'dbfield' => 'vendor',
103             'label' => 'Vendor',
104             'formElement' => 'popup',
105             'selectionList' => ['', 'Amexx', 'Armscor', 'Consolidated', 'Gentine', 'Oroco', 'Praxis', 'Shellalco', 'Subarashii',],
106             'uniquifierField' => 'part_id',
107             },
108             {
109             'dbfield' => 'description',
110             'label' => 'Part Description',
111             'formElement' => 'textarea',
112             'subBr' => 1,
113             'drillDownTruncate' => 60,
114             'uniquifierField' => 'part_id',
115             },
116             {
117             'dbfield' => 'qa_results',
118             'label' => 'QA Results',
119             'formElement' => 'textfield',
120             'linkifyContentOnView' => 1,
121             'uniquifierField' => 'part_id',
122             },
123             {
124             'dbfield' => 'qoh',
125             'label' => 'Quantity',
126             'formElement' => 'textfield',
127             'size' => 5,
128             'uniquifierField' => 'part_id',
129             },
130             {
131             'dbfield' => 'rohs_category',
132             'label' => 'RoHS',
133             'formElement' => 'popup',
134             'selectionList' => ['',1..10],
135             'selectionLabels' => {
136             1 => 'Large and small household appliances',
137             2 => 'IT equipment',
138             3 => 'Telecommunications equipment',
139             4 => 'Consumer equipment',
140             5 => 'Lighting equipment',
141             6 => 'Electronic and electrical tools',
142             7 => 'Toys, leisure, and sports equipment',
143             8 => 'Medical devices',
144             9 => 'Monitoring and control instruments',
145             10 => 'Automatic dispensers',
146             },
147             'uniquifierField' => 'part_id',
148             },
149             {
150             'dbfield' => 'reorder_class',
151             'label' => 'Reorder Class',
152             'formElement' => 'popup',
153             'selectionList' => ['', 'A', 'B', 'C'],
154             'uniquifierField' => 'part_id',
155             },
156             {
157             'dbfield' => 'last_order_date',
158             'label' => 'Last Ordered',
159             'formElement' => 'calendar',
160             'uniquifierField' => 'part_id',
161             },
162             );
163              
164             ######## CGI Controller ##########
165              
166             my $t = CGI->new();
167             print $t->header();
168              
169             my $context = $t->param('context') || 'view';
170              
171             my $table = HTML::EditableTable::Horizontal->new
172             (
173             {
174             'tableFields' => \@tableFields,
175             'width' => '100%',
176             'jsAddData' => 1,
177             'editMode' => $context,
178             'data' => \@tableData,
179             'jsSortHeader' => 1,
180             }
181             );
182              
183             print "
";
184            
185             $table->htmlDisplay();
186            
187             my $nextContext = $context eq 'view' ? 'edit' : 'view';
188              
189             print "";
190             print "";
191              
192              
193             =head1 DESCRIPTION
194              
195             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 like "I have this big complicated spreadsheet that I want to make a website out of..., can you help?". The EditableTable class is an 'abstract' base class and EditableTable::Horizontal and EditableTable::Vertical are the implementations for two commonly used table types. Key features of these classes are as follows:
196              
197             - toggling of the table between view and edit modes with support for common html widgets
198              
199             - uniquification of form element data to support processing of html form submissions of table data
200              
201             - support for rowspanning
202              
203             - 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
204              
205             - support for callbacks when data need to be pre-processed prior to display
206              
207             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.
208              
209             =head1 TABLE METHODS AND PARAMETERS
210              
211             The class methods are designed along 'public' and 'private' lines. The intended use model is indicated in the method descriptions.
212              
213             =cut
214              
215             my $globalTableUid = 1;
216              
217             # valid table parameters
218              
219             my %validTableFieldKeys =
220             (
221             'dbfield' => 1,
222             'label' => 1,
223             'formElement' => 1,
224             'callback' => 1,
225             'selectionList' => 1,
226             'selectionLabels' => 1,
227             'subBr' => 1,
228             'subCommaForBr' => 1,
229             'style' => 1,
230             'editOnly' => 1,
231             'viewOnly' => 1,
232             'suppressCallbackOnEdit' => 1,
233             'align' => 1,
234             'width' => 1,
235             'size' => 1,
236             'minimalEditSize' => 1,
237             'maxLength' => 1,
238             'bgcolor' => 1,
239             'rowspanArrayKey' => 1,
240             'rowspanArrayUniquifier' => 1,
241             'rowspanArrayKeyForUniquification' => 1,
242             'masterCounterUniquify' => 1,
243             'styleHandler' => 1,
244             'default' => 1,
245             'modeModifier' => 1,
246             'tooltip' => 1,
247             'editOnlyOnNegativeValue' => 1,
248             'selectionListCallback' => 1,
249             'htmlSub' => 1,
250             'linkifyContentOnView' => 1,
251             'drillDownTruncate' => 1,
252             'uniquifierField' => 1,
253             'jsClearColumnOnEdit' => 1,
254             'checkBehavior' => 1,
255             );
256              
257             =head2 new (public)
258              
259             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 htmlDisplay(). The following examples detail the minimum requirements for configuring an EditableTable.
260              
261             my $table = EditableTable::Horizontal->new
262             (
263             {
264             'tableFields' => \@tableFields;
265             'data' => \@data,
266             'editMode' => 'edit'
267             }
268             );
269              
270             or
271              
272             my $table = EditableTable::Horizontal->new()
273             $table->setTableFields(\@tableFields);
274             $table->setData(\@data);
275             $table->setEditMode('edit');
276              
277             =cut
278              
279             sub new {
280              
281 0     0 1   my $type = shift @_;
282 0   0       my $class = ref($type) || $type;
283              
284 0           my $self = {};
285              
286             #### public data which needs to be intitialized
287              
288 0           $self->{'tableFields'} = [];
289 0           $self->{'data'} = undef;
290 0           $self->{'editMode'} = undef;
291              
292             #### data which is intended to be private
293              
294             # used for certain column options for name uniquification
295 0           $self->{'masterCounter'} = 1;
296              
297              
298             # default unique table id. Setting this is important if you have more than one table
299 0           $self->{'tableId'} = $globalTableUid++;
300              
301             # used for uniquification of elements in embedded javascript
302             # since multiple tables may be in play, ensure initial value is unique
303             # note each table should embed its own javascript. Do this after 4.0 release
304              
305 0           $self->{'elementUid'} = $self->{'tableId'} + int(rand(1000000)); # BFBA terrible hack
306              
307             # flag to auto-display javascript if required
308 0           $self->{javascriptDisplayed} = 0;
309              
310             # default directory for jsCaldendar when this feature is used
311 0           $self->{calendarDir} = 'jscalendar';
312              
313             # table field parameter validation is on by default
314 0           $self->{validateTableFieldKeys} = 1;
315              
316             # flag set when stdout is rerouted to avoid multiple acts of this
317 0           $self->{stdoutRerouted} = 0;
318            
319 0           my $initData = shift @_;
320              
321 0           bless $self, $class;
322              
323 0 0         if ($initData) {
324 0 0         if (ref($initData) ne 'HASH') { confess "expecting hash reference to initialization data for $class, got a " . ref($initData); }
  0            
325 0           $self->initialize($initData);
326             }
327              
328 0           return $self;
329             }
330              
331             =head2 initialize (private)
332              
333             Peforms validation of data provided to the constuctor and makes set*() calls.
334              
335             =cut
336              
337             sub initialize {
338            
339 0     0 1   my $self = shift @_;
340 0           my $initData = shift @_;
341              
342 0 0         if (exists $initData->{'data'}) { $self->setData(delete $initData->{'data'}); }
  0            
343 0 0         if (exists $initData->{'tableFields'}) { $self->setTableFields(delete $initData->{'tableFields'}); }
  0            
344 0 0         if (exists $initData->{'editMode'}) { $self->setEditMode(delete $initData->{'editMode'}); }
  0            
345 0 0         if (exists $initData->{'sortHeader'}) { $self->setSortHeader(delete $initData->{'sortHeader'}); }
  0            
346 0 0         if (exists $initData->{'jsSortHeader'}) { $self->setJsSortHeader(delete $initData->{'jsSortHeader'}); }
  0            
347 0 0         if (exists $initData->{'sortData'}) { $self->setSortData(delete $initData->{'sortData'}); }
  0            
348 0 0         if (exists $initData->{'tabindex'}) { $self->setTabindex(delete $initData->{'tabindex'}); }
  0            
349 0 0         if (exists $initData->{'title'}) { $self->setTitle(delete $initData->{'title'}); }
  0            
350 0 0         if (exists $initData->{'tableId'}) { $self->setTableId(delete $initData->{'tableId'}); }
  0            
351 0 0         if (exists $initData->{'width'}) { $self->setWidth(delete $initData->{'width'}); }
  0            
352 0 0         if (exists $initData->{'style'}) { $self->setStyle(delete $initData->{'style'}); }
  0            
353 0 0         if (exists $initData->{'jsAddData'}) { $self->setJsAddData(delete $initData->{'jsAddData'}); }
  0            
354 0 0         if (exists $initData->{'noheader'}) { $self->setNoheader(delete $initData->{'noheader'}); }
  0            
355 0 0         if (exists $initData->{'rowspannedEdit'}) { $self->setRowspannedEdit(delete $initData->{'rowspannedEdit'}); }
  0            
356 0 0         if (exists $initData->{'sortOrder'}) { $self->setSortOrder(delete $initData->{'sortOrder'}); }
  0            
357 0 0         if (exists $initData->{'border'}) { $self->setBorder(delete $initData->{'border'}); }
  0            
358 0 0         if (exists $initData->{'suppressUndefinedFields'}) {$self->setSuppressUndefinedFields(delete $initData->{'setSuppressUndefinedFields'}); }
  0            
359 0 0         if (exists $initData->{'calendarDir'}) {$self->setCalendarDir(delete $initData->{'calendarDir'}); }
  0            
360 0 0         if (exists $initData->{'validateTableFieldKeys'}) {$self->setValidateTableFieldKeys(delete $initData->{'validateTableFieldKeys'}); }
  0            
361 0 0         if (exists $initData->{'stringOutput'}) { $self->setStringOutput(delete $initData->{'stringOutput'}); }
  0            
362            
363 0           my @remainingInitKeys = keys %$initData;
364            
365 0 0         if (scalar(@remainingInitKeys)) {
366 0           confess "one or more table parameters are not understood (@remainingInitKeys)";
367             }
368              
369             # further validation
370              
371             # sorting options check - since there are three general ways to sort table data, ensure only one of them is used
372              
373 0 0         if (exists($self->{sortOrder}) + exists($self->{sortHeader}) + exists($self->{jsSortHeader}) > 1) {
374 0           confess "Conflicting sort options have been specified. Only one of 'sortOrder', 'sortHeader', and 'jsSortHeader' may be specified";
375             }
376             }
377              
378             =head2 setValidateTableFieldKeys (public)
379              
380             Toggles validation of field-level parameters. Enabled by default. Disable only if validation is a performance issue.
381              
382             $table->setValidateTableFieldKeys(0)
383              
384             =cut
385              
386             # validation of table field parameters
387              
388             sub setValidateTableFieldKeys {
389 0     0 1   my $self = shift;
390 0           my $val = shift;
391 0           $self->{validateTableFieldKeys} = $self->checkBool($val);
392             }
393              
394             =head2 isvalidTableFieldKey (private)
395              
396             Called for each field-level key parameer if validateTableFieldKeys is enabled (the default).
397              
398             =cut
399              
400             sub isValidTableFieldKey {
401 0     0 0   my $self = shift;
402 0           my $key = shift;
403 0 0         if ($validTableFieldKeys{$key}) { return 1; }
  0            
404 0           else { return 0; }
405             }
406              
407             =head2 getConfigParams (private)
408              
409             this is used by the Javascript Object to determine which javascript code to write for the table
410              
411             =cut
412              
413             # this is used by the Javascript Object to determine which javascript code to write for the table
414              
415             sub getConfigParams {
416 0     0 1   my $self = shift;
417 0           my @params = keys %$self;
418             return \@params
419 0           }
420              
421             =head2 isParamSet (private)
422              
423             this is used by the Javascript Object to determine which javascript code to write for the table
424              
425             =cut
426              
427             sub isParamSet {
428 0     0 1   my $self = shift;
429 0           my $paramName = shift;
430 0 0         if ($self->{$paramName}) { return 1; }
  0            
431 0           else { return 0; }
432             }
433              
434             =head2 setTableId (public)
435              
436             Sets the html 'id' attribute for the top level tab
437              
438             $table->setTableId('catalog_table_2')
439              
440             =cut
441              
442             sub setTableId {
443 0     0 1   my $self = shift;
444 0           my $tableId = shift;
445 0 0         if(!defined($tableId)) { confess "table id is not specified"; }
  0            
446 0           $self->{tableId} = $tableId;
447             }
448              
449             =head2 getTableId (public)
450              
451             Returns the 'tableId', which represents the id attribute
452              
453             $table->getTableId()
454              
455             =cut
456              
457             sub getTableId {
458 0     0 1   my $self = shift;
459 0           return $self->{tableId};
460             }
461              
462             =head2 setData (public)
463              
464             Required parameter. The data structure provided to an EditableTable can take the following forms:
465              
466             array of hashrefs (most common for Horizontal Table)
467              
468             $table->setData(
469             [
470             {
471             'id' => 1001,
472             'name' => 'wiring harness'
473             },
474             {
475             'id' => 1002,
476             'name' => 'wiring harness (new style)'
477             }
478             ]
479             )
480              
481             a hashref of hashrefs - this allows sorting the rows by hash key. This structure is allowable for EditableTable::Horizontal and multi-column Editable::Vertical. See L for details.
482              
483             $table->setData(
484             {
485             '1001' => {
486             'id' => 1001,
487             'name' => 'wiring harness'
488             },
489             '1002' => {
490             'id' => 1002,
491             'name' => 'wiring harness (new style)'
492             }
493             }
494              
495             a hashref - used for single column EditableTable::Vertical
496              
497             $table->setData(
498             {
499             'id' => 1001,
500             'name' => 'wiring harness',
501             }
502             )
503              
504             =cut
505              
506             sub setData {
507 0     0 1   my $self = shift @_;
508 0           my $data = shift @_;
509              
510 0 0 0       unless (ref($data) eq 'ARRAY' || ref($data) eq 'HASH') { confess "data must be an array ref or hash ref, this is a " . ref($data); }
  0            
511              
512 0           $self->{'data'} = $data;
513             }
514              
515             =head2 getData (public)
516              
517             returns the reference to the 'data' parameter
518              
519             =cut
520              
521             sub getData {
522 0     0 1   my $self = shift @_;
523              
524 0           return $self->{'data'};
525             }
526              
527             =head2 setTableFields (public)
528              
529             Required parameter. An arrayref of hashrefs to parameters for each table field. Fields are presented left-to-right in array order for Horizontal tables and top-to-bottom for Vertical Tables. See L for documentation of the field parameters.
530              
531             $table->setTableFields (
532             [
533             {
534             'dbfield' => id,
535             'label' => 'ID#'
536             },
537             {
538             'dbfield' => name,
539             'label' => 'Name'
540             }
541             ];
542              
543             =cut
544              
545             sub setTableFields {
546 0     0 1   my $self = shift @_;
547 0           my $tableFields = shift @_;
548              
549 0 0         unless (ref($tableFields) eq 'ARRAY') { confess "the table field specification must have the form of an array ref of hash refs"; }
  0            
550              
551             # if the table array is not empty, make sure it looks like an array of hashes
552 0 0         if (scalar @$tableFields) {
553 0 0         unless (ref($tableFields->[0]) eq 'HASH') { confess "the table field specification must have the form of an array ref of hash refs"; }
  0            
554             }
555              
556             # basic validation of each field's options. mistyping a field key is a common error in programming EditableTable.
557             # also auto-identify rowspanning data structures
558              
559 0 0         if ($self->{validateTableFieldKeys}) {
560 0           foreach my $field (@$tableFields) {
561 0           foreach my $key (keys %$field) {
562 0 0         if (!$self->isValidTableFieldKey($key)) {
563 0           confess "$key is not a valid table field parameter.";
564             }
565             }
566             }
567             }
568            
569 0           $self->{'tableFields'} = $tableFields;
570             }
571              
572             =head2 getTableFields (public)
573              
574             Returns an arrayref to the 'tableFields' table parameter.
575              
576             =cut
577              
578             sub getTableFields {
579 0     0 1   my $self = shift @_;
580 0           return $self->{tableFields};
581             }
582              
583             =head2 setEditMode (public)
584              
585             Required parameter. Set the table mode to 'view' or 'edit'. In 'view' mode, a table field is reprsented by text. In 'edit' mode, the a field is represented by 'its formElement' parameter.
586              
587             $table->setEditMode('edit');
588              
589             =cut
590              
591             sub setEditMode {
592 0     0 1   my $self = shift @_;
593 0           my $editMode = shift @_;
594              
595 0 0 0       unless ($editMode eq 'edit' || $editMode eq 'view') { confess "edit mode must be 'edit' or 'view'"; }
  0            
596              
597 0           $self->{'editMode'} = $editMode;
598             }
599              
600             =head2 setStringOutput (public)
601              
602             By default, EditableTable's htmlDisplay and htmlJavascriptDisplay method will output to STDOUT. If stringOutput is set to 1 or 'true', then htmlDisplay will return a string with the table html.
603              
604             $table->setStringOutput(1);
605             my $tableHtml = $table->htmlDisplay();
606              
607             =cut
608              
609             sub setStringOutput {
610 0     0 1   my $self = shift @_;
611 0           my $val = shift @_;
612 0           $self->{'stringOutput'} = $self->checkBool($val);
613             }
614              
615             =head2 setSortHeader (public)
616              
617             Set a base url for server-side sorting. See L for details on the sorting options for EditableTable.
618              
619             $table->setSortHeader("http://yoururl.com?session=ruggs98888&");
620              
621             =cut
622              
623             # print a clickable header which sorts by that column
624              
625             sub setSortHeader {
626 0     0 1   my $self = shift @_;
627 0           my $sortHeader = shift @_;
628              
629 0 0         if (ref($sortHeader)) { confess "the sort header is a string with the url leading up to the name of the column you wish to sort by, this is a " . ref($sortHeader); }
  0            
630              
631             # need a check here
632              
633 0           $self->{'sortHeader'} = $sortHeader;
634             }
635              
636             =head2 setSortData (public)
637              
638             When set, EditableTable will sort the $self->{data} server-side. Often this is unecessary as the data will have been presorted using SQL, but for small tables this is less work and is self-contained to the EditableTable class.
639              
640             $table->setSortData(1);
641              
642             =cut
643              
644             sub setSortData {
645 0     0 1   my $self = shift @_;
646 0           my $sortData = shift @_;
647 0           $self->{'sortData'} = $self->checkBool($sortData);
648             }
649              
650             =head2 setJsSortHeader (public)
651              
652             When set, implements javascript for client-side table sorting. See L for details on EditableTable sorting.
653              
654             $table->setSortData(1)
655              
656             =cut
657              
658             # javascript dynamic sort header
659              
660             sub setJsSortHeader {
661 0     0 1   my $self = shift @_;
662 0           my $jsSortHeader = shift @_;
663 0           $self->{'jsSortHeader'} = $self->checkBool($jsSortHeader);
664             }
665              
666             =head2 setTabindex (public)
667              
668             Use when the EditableTable needs to be integrated with other form elements and the tabindex isn't desirable. This sets all the tabindex values for formElemnts to the specified number, which will result in the browser default behavior being applied to the table but in the correct order with other form elements. An reference is used in case the behavior of this method is changed in the future.
669              
670             my $tabindex = 100;
671              
672             $table->setTabindex(\$tabindex);
673              
674             =cut
675              
676             # reference to tab index to integrate table with the form
677              
678             sub setTabindex {
679 0     0 1   my $self = shift @_;
680 0           my $tabindex = shift @_;
681 0 0         if (ref($tabindex) ne 'SCALAR') { confess "the tabindex must be a reference to the tabindex"; }
  0            
682 0           $self->{'tabindex'} = $tabindex;
683             }
684              
685             =head2 setTitle (public)
686              
687             Sets table title using colspanned tag. Note that this method only works on Vertical tables as it conflicts with the javascript client-side sorting.
688              
689             $self->setTitle("Table Title")
690              
691             =cut
692              
693             # title for top of table in a single rowspan
694              
695             sub setTitle {
696 0     0 1   my $self = shift @_;
697 0           my $title = shift @_;
698 0 0         if (ref($title)) { confess "the title is a string with the table title"; }
  0            
699 0           $self->{'title'} = $title;
700             }
701              
702             # table width
703              
704             =head2 setWidth (public)
705              
706             sets the 'width' parameter in pixels. Alternatively, use setStyle.
707              
708             $table->setWidth(1024);
709              
710             =cut
711              
712             sub setWidth {
713 0     0 1   my $self = shift @_;
714 0           my $width = shift @_;
715 0 0 0       if (ref($width) || $width !~ /\d+/) { confess "not a number!"; }
  0            
716 0           $self->{'width'} = $width;
717             }
718              
719             =head2 setBorder (public)
720              
721             sets the 'border' param. Alternatively, use setStyle.
722              
723             $table->setBorder(1);
724              
725             =cut
726              
727             # table border
728              
729             sub setBorder {
730 0     0 1   my $self = shift @_;
731 0           my $border = shift @_;
732 0 0 0       if (ref($border) || $border !~ /\d+/) { confess "not a number!"; }
  0            
733 0           $self->{'border'} = $border;
734             }
735              
736             =head2 setStyle (public)
737              
738             sets 'style' attribute. "; "; "; "; "; ";
739            
740             $table->setStyle("border-width:1px;");
741              
742             =cut
743              
744             sub setStyle {
745 0     0 1   my $self = shift @_;
746 0           my $style = shift @_;
747 0 0         if (ref($style)) { confess "style is not a string"; }
  0            
748 0           $self->{'style'} = $style;
749             }
750            
751             =head2 setJsAddData (public)
752              
753             For Horizontal tables. Use to activate javascript to support addition of new table rows. See L for details.
754              
755             $table->setJsAddData(1);
756              
757             =cut
758              
759             # indicates that a button to insert a row of data into the table is to be provided when in edit mode
760              
761             sub setJsAddData {
762 0     0 1   my $self = shift @_;
763 0           my $jsAddData = shift @_;
764 0           $self->{'jsAddData'} = $self->checkBool($jsAddData);
765             }
766              
767             =head2 setNoHeader (public)
768              
769             For Horizontal Tables. Suppresses the table header row.
770              
771             $self->setNoHeader(1);
772              
773             =cut
774              
775             # suppresses column headings
776              
777             sub setNoHeader {
778              
779 0     0 1   my $self = shift @_;
780 0           my $noheader = shift @_;
781 0           $self->{'noheader'} = $self->checkBool($noheader);
782             }
783              
784             =head2 setRowspannedEdit (public)
785              
786             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 desired behavior, use this method to preserve the rowspanning in 'edit' mode.
787              
788             $table->setRowspannedEdit(1);
789              
790             =cut
791              
792             sub setRowspannedEdit {
793              
794 0     0 1   my $self = shift @_;
795 0           my $rowspannedEdit = shift @_;
796 0           $self->{'rowspannedEdit'} = $self->checkBool($rowspannedEdit);
797              
798             }
799              
800             =head2 setSortOrder (public);
801              
802             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.
803              
804             $table->setSortOrder(['UX34IG' , 'UX45ZZ', 'RG01IG']);\
805              
806             =cut
807              
808             # horizontal tables provided with hash of hashes: an array ref to key list used to sort hash of hashes
809              
810             sub setSortOrder {
811              
812 0     0 1   my $self = shift @_;
813 0           my $sortOrder = shift @_;
814 0 0         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"; }
  0            
815              
816 0           $self->{'sortOrder'} = $sortOrder;
817             }
818              
819             =head2 setSuppressUndefinedFields (public)
820              
821             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.
822              
823             $table->setSuppressUndefinedFields(1)
824              
825             =cut
826              
827             # block display of undefined fields - vertical table only
828              
829             sub setSuppressUndefinedFields {
830 0     0 1   my $self = shift @_;
831 0           my $suppressUndefinedFields = shift @_;
832 0           $self->{'suppressUndefinedFields'} = $self->checkBool($suppressUndefinedFields);
833             }
834              
835             =head2 getCalendarDir (public)
836              
837             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.
838            
839             $self->getCalendarDir();
840              
841             =cut
842              
843             sub getCalendarDir {
844 0     0 1   my $self = shift @_;
845 0           return $self->{calendarDir};
846             }
847              
848             =head2 setCalendarDir (public)
849              
850             Directory for installation of www.dynarch.com jscalendar-1.0, which is supported in the 'calendar' formElement. Defaults to 'jscalendar' if not set.
851            
852             $self->setCalendarDir('jscal_10');
853              
854             =cut
855              
856             sub setCalendarDir {
857 0     0 1   my $self = shift @_;
858 0   0       my $calendarDir = shift @_ || confess "missing calendar directory";
859 0           $self->{calendarDir} = $calendarDir;
860             }
861              
862             =head2 htmlDisplay (public)
863              
864             This method renders the html to STDOUT. If table requirements are not met, an exception will occur when this method is called.
865              
866             $table->htmlDisplay()
867              
868             =cut
869              
870             sub htmlDisplay {
871              
872 0     0 1   my $self = shift @_;
873              
874             # re-route STDOUT to string if called for
875            
876 0           my $stdout = undef;
877              
878 0 0 0       if ($self->{stringOutput} && !$self->{stdoutRerouted}) {
879              
880 0 0         open(TMPOUT, '>&', \*STDOUT) || confess "failed to save STDOUT";
881 0           close STDOUT;
882 0 0         open(STDOUT, '>', \$stdout) || confess "failed to reroute STDOUT to string";
883            
884             # set flag nested calls don't redo this
885 0           $self->{stdoutRerouted} = 1;
886             }
887            
888             # display javascript - this method will return immediately if the javascriptDispalyed flag is set.
889              
890 0           $self->htmlJavascriptDisplay();
891              
892             # ensure we have all the required parameters populated before attempting to create the table html
893              
894 0 0         if (!$self->{'data'}) { confess "table parameters are incompelete - missing data!"; }
  0            
895 0 0         if (!$self->{'tableFields'}) { confess "table parameters are incompelete - missing table view specification!"; }
  0            
896 0 0         if (!$self->{'editMode'}) { confess "table parameters are incompelete - missing edit mode!"; }
  0            
897              
898             # print a hidden template row if the jsAddData feature is being used
899              
900 0 0         if ($self->{jsAddData}) {
901 0           $self->htmlAddDataSetup();
902             }
903              
904             # sort the data server-side if this option is used
905             # many times this is the best approach, but sometimes the calling server code will have the data
906             # already sorted via SQL
907              
908 0 0 0       if (CGI::param('orderByAsc') && $self->{sortData}) {
    0 0        
909              
910 0           my @sortedTableData = sort {$a->{CGI::param('orderByAsc')} cmp $b->{CGI::param('orderByAsc')}} @{$self->{data}};
  0            
  0            
911 0           $self->{data} = \@sortedTableData;
912             }
913             elsif (CGI::param('orderByDesc') && $self->{sortData}) {
914              
915 0           my @sortedTableData = sort {$b->{CGI::param('orderByDesc')} cmp $a->{CGI::param('orderByDesc')}} @{$self->{data}};
  0            
  0            
916 0           $self->{data} = \@sortedTableData;
917             }
918              
919             # virtual method which must be inherited for core table drawing
920              
921 0           $self->makeTable();
922              
923             # if stdout rerouted
924 0 0         if ($self->{stringOutput}) {
925 0           open STDOUT, '>&', \*TMPOUT;
926 0           close TMPOUT;
927              
928 0           $self->{stdoutRerouted} = 0;
929              
930 0           return $stdout;
931             }
932             }
933              
934             =head2 removeField (public)
935              
936             Use to elminate a member of the 'tableFields'. Returns the table field hashref. Requires the key and the value to identify the field to remove
937              
938             $table->removeField('dbfield', 'partId');
939             $table->removeField('label', 'Part Id#');
940              
941             =cut
942              
943             sub removeField {
944              
945 0     0 1   my $self = shift @_;
946 0           my $colSpecField = shift @_;
947 0   0       my $colSpecValue = shift @_ || confess "must provide key/value for colspec column to remove";
948              
949 0           my $tableFields = $self->{tableFields};
950              
951 0           for (my $i=0; $i
  0            
952 0           my $colSpec = $tableFields->[$i];
953 0 0         if ($colSpec->{$colSpecField} eq $colSpecValue) {
954 0           splice @{$tableFields},$i,1;
  0            
955 0           return $colSpec; # return it in case user wants to reuse it
956             }
957             }
958              
959 0           return undef;
960             }
961              
962             =head2 setColumnLabel (public)
963              
964             Given a 'dbfield' table field value, replaces the 'label' parameter with the supplied value.
965              
966             $table->setColumnLabel('partId', 'Part Id#');
967              
968             =cut
969              
970             sub setColumnLabel {
971              
972 0     0 1   my $self = shift @_;
973 0           my $dbfield = shift @_;
974 0           my $newLabel = shift @_;
975              
976 0           my $tableFields = $self->{tableFields};
977              
978 0           for (my $i=0; $i
  0            
979 0           my $colSpec = $tableFields->[$i];
980 0 0         if ($colSpec->{dbfield} eq $dbfield) {
981 0           $colSpec->{label} = $newLabel;
982 0           return;
983             }
984             }
985              
986 0           return undef;
987              
988             }
989              
990             =head2 shiftField (public)
991              
992             shifts and returns a 'tableField'
993              
994             my $field = $table->shiftField();
995              
996             =cut
997              
998             sub shiftField {
999              
1000 0     0 1   my $self = shift @_;
1001 0           my $fieldRef =shift @{$self->{'tableFields'}};
  0            
1002              
1003 0           return $fieldRef;
1004             }
1005              
1006             =head2 unshiftField (public)
1007              
1008             unshifts the 'tableFields' with the provided tableField hashref.
1009              
1010             my $field = { 'dbfield' => 'partId', 'label' => 'Part Id#' };
1011             $table->unshiftField($field);
1012              
1013             =cut
1014              
1015             sub unshiftField {
1016              
1017 0     0 1   my $self = shift @_;
1018 0           my $colSpec = shift @_;
1019 0 0         if (ref($colSpec) ne 'HASH') { confess "field spec is not in the correct format - expecting a hash reference"; }
  0            
1020              
1021 0           unshift @{$self->{'tableFields'}}, $colSpec;
  0            
1022             }
1023              
1024             =head2 setJavascript (public)
1025              
1026             This method can be used to override the default javascript object. Inherit from HTML::EditableTable::Javascript and provide an object reference to your table prior to calling htmlJavascriptDisplay(). If this method is not used, the default class will be used to create a javascript object. See L for more details.
1027              
1028             my $javascript = MyJavascript->new();
1029             $table->setJavascript($javascript);
1030              
1031             =cut
1032              
1033             sub setJavascript {
1034 0     0 1   my $self = shift;
1035 0           my $javascript = shift;
1036            
1037 0 0         if (!$javascript->isa('HTML::EditableTable::Javascript')) { confess 'reference provided is not a derivative of HTML::EditableTable::Javascript'; }
  0            
1038            
1039 0           $self->{javascript} = $javascript;
1040             }
1041              
1042             =head2 htmlJavascriptDisplay (public, but normally called automatically)
1043              
1044             Call it only if there is a need to control where javascript code is placed in the html. Prints the \n";
1712              
1713 0           print "
1714              
1715             }
1716             elsif ($colSpec->{'formElement'} eq 'deleteRowButton') {
1717 0           print "";
1718 0           print "";
1719 0           print "
1720             }
1721             else {
1722 0           confess join ":", "Unknown form element ($colSpec->{'formElement'})!", __FILE__, __LINE__;
1723             }
1724             }
1725             elsif (exists $colSpec->{'htmlSub'}) {
1726 0           my $html = $colSpec->{'htmlSub'};
1727 0           $html =~ s/<$colSpec->{'dbfield'}>/$cellValue/g;
1728 0           print "" . $html . "
1729             }
1730             else {
1731              
1732 0           my $name;
1733             # if we have a list and labels for a popup, assume the provided value is an index to the label;
1734              
1735 0 0 0       if (exists $colSpec->{'selectionList'} && exists $colSpec->{'selectionLabels'}) {
1736              
1737 0           print "" . $colSpec->{'selectionLabels'}->{$cellValue} . "
1738             }
1739             else {
1740              
1741             # sometimes we want to substitue
for newlines
1742              
1743 0           my $content = $cellValue;
1744              
1745 0 0         if ($colSpec->{'subBr'}) {
1746 0           $content =~ s/\n/\/g;
1747             }
1748              
1749             # if the content is short is short enough an contains thinks that looks like links, convert them to links
1750              
1751 0 0         if ($colSpec->{'linkifyContentOnView'}) {
1752              
1753 0           (my @links) = $content =~ /(http\:\/\/\S+)/g;
1754 0           foreach my $link (@links) {
1755            
1756             # escape $link
1757 0           $link =~ s/\//\\\//g;
1758 0           $link =~ s/\?/\\\?/g;
1759            
1760 0           $content =~ s/($link)/\$1\<\/a\>/;
1761             }
1762             }
1763            
1764             # sometimes we want to substitue
for newlines
1765 0 0         if ($colSpec->{'subBr'}) {
1766 0           $content =~ s/\n/\/g;
1767             }
1768              
1769             # some large text displays are truncated or provided with expandable links
1770              
1771 0 0 0       if ($colSpec->{'drillDownTruncate'} && length($content) > $colSpec->{'drillDownTruncate'}) {
1772              
1773 0           (my $truncatedContent) = $content =~ /(.{0,$colSpec->{'drillDownTruncate'}})/;
1774 0           $truncatedContent .= "...";
1775              
1776 0           my $tdUid = $self->{'elementUid'}++;
1777 0           my $divUid = $self->{'elementUid'}++;
1778 0           my $truncatedDivUid = $self->{'elementUid'}++;
1779 0           my $aUid = $self->{'elementUid'}++;
1780              
1781 0           print "" . "
" . $truncatedContent . "
" . "" . "
1782             }
1783              
1784             else {
1785            
1786 0 0         if (!defined($content)) { $content = ''; }
  0            
1787            
1788 0           print "" . $content . "
1789             }
1790             }
1791             }
1792             }
1793             }
1794              
1795             =head1 TABLE FIELD PARAMETERS
1796              
1797             =head2 dbfield (frequent)
1798              
1799             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.
1800              
1801             'dbfield' => part_id
1802              
1803             =head2 label (frequent)
1804              
1805             Specifies the table column header for horizontal tables and the first colum for vertical tables.
1806            
1807             'label' => 'Part Id#'
1808              
1809              
1810             =head2 formElement (frequent)
1811              
1812             formElement specifies the html input field that will be used when the table is in 'edit' mode. Valid values are
1813              
1814             =over
1815              
1816             =item *
1817              
1818             calendar - Implements popup calendar using www.dynarch.com jscalendar 1.0. Requires this javascript library to be accessible. See L for details.
1819              
1820             =item *
1821              
1822             checkbox - Implements checkbox html element
1823              
1824             =item *
1825              
1826             deleteRowButton - Combine with jsAddRow Table-level feature. Provides button to delete a table row.
1827              
1828             =item *
1829              
1830             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.
1831              
1832             =item *
1833              
1834             hidden - Implements hidden element type.
1835              
1836             =item *
1837              
1838             popup - implements CGI "poup"
1839              
1840             =item *
1841              
1842             scrollingList - implments CGI "scrolling_list"
1843              
1844             =item *
1845              
1846             textarea - implements textarea HTML element
1847              
1848             =item *
1849              
1850             textfield - implements html input of type 'text'
1851              
1852             =back
1853              
1854             'formElement' => 'checkbox'
1855              
1856             =head2 selectionList (frequent)
1857              
1858             An array reference to a list of values to display in a popup or scrollingList
1859              
1860             'selectionList' => [ '34GXT', '35TTG', '56YUG' ]
1861              
1862             =head2 selectionLabels (frequent)
1863              
1864             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.
1865              
1866             'selectionLabels' => { '34GXT' => 'Big Widget', '35TTG' => 'Medium Widget', '56YUG' => 'Small Widget' }
1867              
1868             =head2 editOnly (frequent)
1869              
1870             Don't display this field when the table is in 'view' mode.
1871              
1872             'editOnly' => 1
1873              
1874             =head2 viewOnly (frequent)
1875              
1876             Don't display this field when the table is in 'edit' mode.
1877              
1878             'viewOnly' => 1
1879              
1880             =head2 default (frequent)
1881              
1882             Provides default value for form elements when no value is present in the data
1883              
1884             'default' => 'UX56SG'
1885              
1886             =head2 uniquifierField (frequent)
1887              
1888             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 dbfield. Typically, the 'dbfield' is a database table's unique 'id' field.
1889              
1890             my @tableFields = (
1891             {
1892             'dbfield' => 'part_id'
1893             }
1894             {
1895             'dbfield' => 'description',
1896             'uniquifierField' => 'part_id'
1897             'formElement' => 'textfield',
1898             }
1899             );
1900              
1901             with data
1902            
1903             my @data = (
1904             'part_id' => 1234
1905             )
1906              
1907             will produce
1908            
1909            
1910              
1911             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.
1912              
1913             =head2 drillDownTruncate (occasional)
1914              
1915             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.
1916              
1917             'drillDownTruncate' => 100
1918              
1919             =head2 callback (occasional)
1920              
1921             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.
1922              
1923             'callback' => &callbackFunction
1924              
1925             sub callbackFunction {}
1926              
1927             or
1928              
1929             'callback' => $self->callbackClosure()
1930              
1931             sub callbackClosure {
1932              
1933             my $fp = function {
1934             # need the object to do something at callback time
1935             $self->doSomething()
1936             };
1937              
1938             return $fp;
1939             }
1940              
1941             The callback interface is as follows
1942            
1943             &$fp(
1944             $row, # hashref to the current row of data
1945             $fieldParams, # hashref to the current field parameters
1946             $editMode, # table mode - 'view' or 'edit'
1947             $rowspanSubcounter # used only for tables using rowspanning, provides rowspan count for current rowspan instance
1948             );
1949              
1950              
1951             =head2 checkBehavior (occasional)
1952              
1953             Determines how checkboxes will behave when toggling between view and edit modes. Valid values are 'checked', 'checkedOnVal', and checkedOnTrue'. The later value will result in the checkbox being checked if the data value matches the pattern /^1|true|yes$/i
1954              
1955             'checkBehavior' => 'checkedOnTrue'
1956              
1957             =head2 suppressCallbackOnEdit (occasional)
1958              
1959             Prevents the call to the callback when the table is in 'edit' mode
1960              
1961             'suppressCallbackOnEdit' => 1
1962              
1963             =head2 subBr (occasional)
1964              
1965             Sometimes used to sub
tags for \n when presenting text data. Common use case is when a user cut and pastes an email into a textarea. In 'view' mode, 'subBr' will keep the email's formatting
1966              
1967             =head2 style (occasional)
1968              
1969             Used to provide a css style to the element used to present a data value. This parameter can be used in lieu of several others - align, bgcolor
1970              
1971             'style' => "font-family:'Times New Roman';font-size:20px;"
1972              
1973             =head2 align (occasional)
1974              
1975             Sets the horizontal 'align' html parameter. Valid values are 'left', 'right', and 'center'.
1976              
1977             'align' => 'center'
1978              
1979             =head2 width (occasional)
1980              
1981             Sets the desired column width in pixels. Sets the 'width' parameter.
1982              
1983             'width' => 70
1984              
1985             =head2 size (occasional)
1986              
1987             Sets a textfield input tags 'size' parameter for the number of displayed characters. Applicable only in 'edit' mode. See also maxLength.
1988              
1989             'size' => 60
1990              
1991             =head2 maxLength (occasional)
1992              
1993             For a textfield input tag, sets the maximum number of characters which can be input. Sets the 'maxLength' html tag parameter. This defaults to 255 if no value is provided, the reasoning being the most textfields are mapped to a VARCHAR database datatype with a 255 char limit.
1994              
1995             'maxLength' => 64
1996              
1997             =head2 tooltip (occasional)
1998              
1999             Implements javascript to provide mouseover tooltips. The value sets the text to be displayed. See the L section for details.
2000              
2001             'tooltip' => 'The master PDM part number'
2002              
2003             =head2 linkifyContentOnView (occasional)
2004              
2005             For text data, this feature creates matching hyperlinks out of any text beginning with "http://". This is not done in 'edit' mode.
2006              
2007             'linkifyContentOnView' => 1
2008              
2009             =head2 subCommaForBr (rare)
2010              
2011             For'poorly' normalized data presentation where a
tag is preferable to a ',' in text display
2012              
2013             'subCommaForBr' => 1
2014              
2015             =head2 jsClearColumnOnEdit (rare)
2016              
2017             For horizontal tables only. Provides a 'clear column' button in the column header to clear text from all fields in the column when the table is in 'edit' mode.
2018              
2019             'jsClearColumnOnEdit' => 1,
2020              
2021             =head2 minimalEditSize (rare)
2022              
2023             In table 'edit' mode, the default textfield width is 2X the value with a minimum value of 60 char. If 'minimalEditSize' is set, this reduces to 1.2X the value with a minium value of 15. In large tables, minimalEditSize helps keep the presentation clean.
2024              
2025             'minimalEditSize' => 1
2026              
2027             =head2 bgcolor (rare)
2028              
2029             Sets the background color of cell be setting the 'bgcolor' parameter
2030              
2031             'bgcolor' => '#ff0000'
2032              
2033             =head2 rowspanArrayKey (rare)
2034              
2035             Rowspanning is sometimes done by creators of spreadsheets that are to be converted to web applications. The data for these cases is a hierarchical array of hashrefs. This parameter provides the name of the second level hash key which contains the nested data to be rowspanned. The 'dbfield' parameter is used to specify the top level key to the second level of data to be rowspanned.
2036              
2037             @rowspannedData = (
2038             {
2039             'part_id' => 'UX34GT',
2040             'sub_data' => [
2041             {
2042             'sub_assembly_name' => '34R',
2043             },
2044             {
2045             'sub_assembly_name' => '23D',
2046             }
2047             ]
2048             },
2049             {
2050             'part_id' => 'RT67IV',
2051             'sub_data' => [
2052             {
2053             'sub_assembly_name' => '14G',
2054             },
2055             {
2056             'sub_assembly_name' => '13R',
2057             }
2058             ]
2059             }
2060             );
2061              
2062             @tableFields = (
2063             {
2064             'dbfield' => 'part_id',
2065             'label' => 'Part ID#'
2066             },
2067             {
2068             'dbfield' => 'sub_data',
2069             'rowspanArrayKey' => 'sub_assembly_name',
2070             'label' => 'Part Sub-Assembly',
2071             }
2072             );
2073              
2074             will produce at table like this
2075              
2076             ---------------------------------
2077             | Part ID# | Part Sub-Assembly |
2078             |-------------------------------|
2079             | | 34R |
2080             | UX34GT |-------------------|
2081             | | 23D |
2082             |-------------------------------|
2083             | | 14G |
2084             | RT67IV |-------------------|
2085             | | 13R |
2086             ---------------------------------
2087              
2088             In 'edit' mode, the table is flattened to support row addition and deletion.
2089              
2090             =head2 rowspanArrayUniquifier (rare)
2091              
2092             For rowspanning tables. In 'edit' mode, it is critical to produce a traceable field id for elements that are rowspanned. The most simple way to do this is by specifing this parameter to be a member of the nested rowspanned data. Working from the previous example:
2093              
2094             @tableFields = (
2095             {
2096             'dbfield' => 'part_id',
2097             'label' => 'Part ID#'
2098             },
2099             {
2100             'dbfield' => 'sub_data',
2101             'rowspanArrayKey' => 'sub_assembly_name',
2102             'label' => 'Part Sub-Assembly',
2103             'rowspanArrayUniquifier' => 'sub_assembly_name'
2104             }
2105             );
2106              
2107             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 flag 'rowspannedEdit'.
2108              
2109             -------------------------------------------------------
2110             | Part ID# | Part Sub-Assembly |
2111             |----------|------------------------------------------|
2112             | UX34GT | 34R (sub_assembly_name_34R) |
2113             |----------|------------------------------------------|
2114             | UX34GT | 23D (sub_assembly_name_23D) |
2115             |----------|------------------------------------------|
2116             | RT67IV | 14G (sub_assembly_name_14G |
2117             |----------|------------------------------------------|
2118             | RT67IV | 13R (sub_assembly_name_13R) |
2119             -------------------------------------------------------
2120              
2121             This parameter can be combined with uniquifierField to produce a expanded unique id. This is handy when the rowspanned data cannot produce a unique field id or a binding relationship needs to be maintained.
2122              
2123             @tableFields = (
2124             {
2125             'dbfield' => 'part_id',
2126             'label' => 'Part ID#'
2127             'uniquifierField' => 'part_id'
2128             },
2129             {
2130             'dbfield' => 'sub_data',
2131             'rowspanArrayKey' => 'sub_assembly_name',
2132             'label' => 'Part Sub-Assembly',
2133             'uniquifierField' => part_id,
2134             'rowspanArrayUniquifier' => 'sub_assembly_name'
2135             }
2136             );
2137              
2138             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'.
2139              
2140             -----------------------------------------------------------------------------
2141             | Part ID# | Part Sub-Assembly |
2142             |-------------------------|-------------------------------------------------|
2143             | UX34GT (part_id_UX34GT) | 34R (sub_assembly_name_UX34G_34R) |
2144             |-------------------------|-------------------------------------------------|
2145             | UX34GT (part_id_UX34GT) | 23D (sub_assembly_name_UX34GT_23D) |
2146             |-------------------------|-------------------------------------------------|
2147             | RT67IV (part_id_RT67IV) | 14G (sub_assembly_name_RT67IV_14G) |
2148             |-------------------------|-------------------------------------------------|
2149             | RT67IV (part_id_RT67IV) | 13R (sub_assembly_name_RT67IV_13R) |
2150             -----------------------------------------------------------------------------
2151              
2152              
2153             This parameter can also be arrayref to a list of second-level data keys if additional keys are required to acheive a unique name. For example
2154              
2155              
2156             =head2 rowspanArrayKeyForUniquification (rare)
2157              
2158             When rowspanned tables are switched to 'edit' mode, the table is flattened to provide the ability to change the relationships which create the spanning. To provide traceability for the fields which span, combine this parameter with rowspanArrayUniquifier (described above) to specify the first and second level keys of the data hierarchy to used for field name and id construction.
2159              
2160             given data
2161              
2162             @tableFields = (
2163             {
2164             'dbfield' => 'part_id',
2165             'label' => 'Part ID#'
2166             'uniquifierField' => 'part_id',
2167             'rowspanArrayKeyforUniquification' => 'sub_data',
2168             'rowspanArrayUniquifier' => 'sub_assembly_name'
2169             },
2170             {
2171             'dbfield' => 'sub_data',
2172             'rowspanArrayKey' => 'sub_assembly_name',
2173             'label' => 'Part Sub-Assembly',
2174             'uniquifierField' => part_id,
2175             'rowspanArrayUniquifier' => 'sub_assembly_name'
2176             }
2177             );
2178              
2179             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'.
2180              
2181             ---------------------------------------------------------------------------------
2182             | Part ID# | Part Sub-Assembly |
2183             |-----------------------------|-------------------------------------------------|
2184             | UX34GT (part_id_UX34GT_34R) | 34R (sub_assembly_name_UX34G_34R) |
2185             |-----------------------------|-------------------------------------------------|
2186             | UX34GT (part_id_UX34GT_23D) | 23D (sub_assembly_name_UX34GT_23D) |
2187             |-----------------------------|-------------------------------------------------|
2188             | RT67IV (part_id_RT67IV_14G) | 14G (sub_assembly_name_RT67IV_14G) |
2189             |-----------------------------|-------------------------------------------------|
2190             | RT67IV (part_id_RT67IV_13R) | 13R (sub_assembly_name_RT67IV_13R) |
2191             ---------------------------------------------------------------------------------
2192              
2193             =head2 masterCounterUniquify (rare)
2194              
2195             The final field name and id generation parameter. If specified, uses the 'class static' field counter to append a final id to the field name. Useful when adding rows of new data to a table where this is needed to ensure a unique id is created.
2196              
2197             given data
2198              
2199             @tableFields = (
2200             {
2201             'dbfield' => 'part_id',
2202             'label' => 'Part ID#'
2203             },
2204             {
2205             'dbfield' => 'sub_data',
2206             'rowspanArrayKey' => 'sub_assembly_name',
2207             'label' => 'Part Sub-Assembly',
2208             'masterCounterUniquify' => 1
2209             }
2210             );
2211              
2212             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'.
2213              
2214             ------------------------------------------------------
2215             | Part ID# | Part Sub-Assembly |
2216             |----------|-----------------------------------------|
2217             | UX34GT | 34R (sub_assembly_name_1) |
2218             |----------|-----------------------------------------|
2219             | UX34GT | 23D (sub_assembly_name_2) |
2220             |----------|-----------------------------------------|
2221             | RT67IV | 14G (sub_assembly_name_3) |
2222             |----------|-----------------------------------------|
2223             | RT67IV | 13R (sub_assembly_name_4) |
2224             ------------------------------------------------------
2225              
2226             =head2 styleHandler (rare)
2227              
2228             Callback mechanism to provide a style for the tag.
2229              
2230             'styleHandler' => &getStyle
2231              
2232             The interface for the callback is
2233              
2234             &getStyle(
2235             $row # hashref to the current data set
2236             )
2237              
2238             =head2 modeModifier (rare)
2239              
2240             Provides a callback mechanism to determine if the mode ('view' or 'edit') should be changed for this field only
2241              
2242             'modeModifer' => &getMode
2243              
2244             The interface for this callback is as follows:
2245              
2246             &getMode (
2247             $editMode, # 'view' or 'edit'
2248             $data # hashref to the current dataset (row or column, depending on table type)
2249             )
2250              
2251             The callback must return 'view' or 'edit'.
2252              
2253             =head2 editOnlyOnNegativeValue (rare)
2254            
2255             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
2256              
2257             'editOnlyOnNegativeValue' => 1,
2258              
2259             =head2 selectionListCallback (rare)
2260              
2261             Specifies a callback to produce a value list for a popup or scrollingList element. Useful when a dynamic query or calculation must be made.
2262              
2263             'selectionListCallback' => &getListValues
2264              
2265             The interface for this call back is as follows:
2266              
2267             &getListValues (
2268             $dataSet # current row or column data hashref
2269             );
2270              
2271             The callback must provide an arrayref of values.
2272              
2273             =head2 htmlSub (rare)
2274              
2275             provides a simple mechanism to substitute the current field's value into a string template
2276              
2277             'htmlSub' => "http://www.site.com?value="
2278              
2279             The '' text will be replaced by the current dataset's $data->{dbfield}
2280              
2281             =head1 JAVASCRIPT INTEGRATION
2282              
2283             =head2 Overview
2284              
2285             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 "under the hood" via table and table-field parameter setting. If not specified by the user, the Javascript object is created by the table. This leads to the the javascript