Merge branch 'patch-7' of https://github.com/gjones2/Freeside (#13854 as this bug...
[freeside.git] / httemplate / elements / auto-table.html
1 <%doc>
2 (within a form)
3 <table>
4 <tr>
5   <th>Field 1</th>
6   <th>Field 2</th>
7 </tr>
8 <tr id="mytemplate">
9   <td><input type="text" name="field1"></td>
10   <td><select name="field2">...</td>
11   ...
12 </tr>
13 </table>
14 <& /elements/auto-table.html,
15   table => 'mytable',
16   template_row = 'mytemplate',
17   rows => [
18             { field1 => 'foo', field2 => 'CA', ... },
19             { field1 => 'bar', field2 => 'TX', ... }, ...
20           ],
21 &>
22
23   or if you prefer:
24 ...
25   fieldorder => [ 'field1', 'field2', ... ],
26   rows => [
27             [ 'foo', 'CA' ],
28             [ 'bar', 'TX' ],
29           ],
30
31 In the process/ handler, something like:
32 my @rows;
33 my %vars = $cgi->Vars;
34 for my $k ( keys %vars ) {
35   $k =~ /^${pre}magic(\d+)$/ or next;
36   my $rownum = $1;
37   # find all submitted names ending in this rownum
38   my %thisrow = 
39     map { $_ => $vars{$_} } 
40     grep /^(.*[\d])$rownum$/, keys %vars;
41   $thisrow->{num} = delete $thisrow{"${pre}magic$rownum"};
42   push @rows, $thisrow;
43 }
44 </%doc>
45 <tbody id="<%$pre%>autotable"></tbody>
46 <script type="text/javascript">
47 var <%$pre%>template;
48 var <%$pre%>tbody;
49 var <%$pre%>next_rownum;
50 var <%$pre%>set_rownum;
51 var <%$pre%>addRow;
52 var <%$pre%>deleteRow;
53 var <%$pre%>fieldorder = <% to_json($fieldorder) %>;
54
55 function <%$pre%>possiblyAddRow_factory(obj) {
56   var callback = obj.onchange;
57   return function() {
58     if ( obj.rownum == <%$pre%>tbody.lastChild.rownum ) {
59       // then this is the last row, and it's being changed, so spawn a new row
60       <%$pre%>addRow();
61     }
62     if ( callback ) {
63       callback.apply(obj);
64     }
65   }
66 }
67
68 function <%$pre%>set_rownum(obj, rownum) {
69   obj.rownum = rownum;
70   if ( obj.id ) {
71     obj.id = obj.id + rownum;
72   }
73   if ( obj.name ) {
74     obj.name = obj.name + rownum;
75     // also, in this case it's a form field that will be part of the record
76     // so set up an onchange handler
77     obj.onchange = <%$pre%>possiblyAddRow_factory(obj);
78   }
79   for (var i = 0; i < obj.children.length; i++) {
80     if ( obj.children[i] instanceof Node ) {
81       <%$pre%>set_rownum(obj.children[i], rownum);
82     }
83   }
84 }
85
86 function <%$pre%>addRow(data) {
87   // duplicate the node
88   // warning: cloneNode doesn't clone event handlers that were set through 
89   // the DOM
90   // if 'data' is an object, prepopulate the row's fields with the object's
91   // elements
92   // returns the rownum of the new row
93   var row = <%$pre%>template.cloneNode(true);
94   <%$pre%>tbody.appendChild(row);
95   var this_rownum = <%$pre%>next_rownum;
96   <%$pre%>set_rownum(row, this_rownum);
97   if(data instanceof Array) {
98     for (i = 0; i < data.length && i < <%$pre%>fieldorder.length; i++) {
99       var el = document.getElementsByName(<%$pre%>fieldorder[i] + this_rownum)[0];
100       if (el) {
101         el.value = data[i];
102       }
103     }
104   } else if (data instanceof Object) {
105     for (var field in data) {
106       var el = document.getElementsByName(field + this_rownum)[0];
107       if (el) {
108         el.value = data[field];
109 %       # doesn't work for checkbox
110       }
111     }
112   } // else nothing
113   <%$pre%>next_rownum++;
114   return this_rownum;
115 }
116
117 function <%$pre%>deleteRow(rownum) {
118   if ( rownum == <%$pre%>tbody.lastChild.rownum ) {
119     // if this is the last row, spawn another one after it
120     <%$pre%>addRow();
121   }
122   var r = document.getElementById('<%$pre%>row' + rownum);
123   <%$pre%>tbody.removeChild(r);
124 }
125
126 function <%$pre%>init() {
127   <%$pre%>template = document.getElementById(<% $template_row |js_string%>);
128   <%$pre%>tbody = document.getElementById('<%$pre%>autotable');
129   <%$pre%>next_rownum = <%$pre%>template.sectionRowIndex;
130   // detach the template row
131   var table = <%$pre%>template.parentNode;
132   table.removeChild(<%$pre%>template);
133   // give it an id
134   <%$pre%>template.id = <%$pre |js_string%> + 'row';
135   // and a magic identifier so we know it's been submitted
136   var magic = document.createElement('INPUT');
137   magic.setAttribute('type', 'hidden');
138   magic.setAttribute('name', '<%$pre%>magic');
139   magic.value = '1';
140   // and a delete button
141 %# should this be enclosed in an actual <button> for aesthetics?
142   var delete_button = document.createElement('IMG');
143   delete_button.id = 'delete_button';
144   delete_button.src = '<%$fsurl%>images/cross.png';
145   delete_button.alt = 'X';
146   // use an inline string for this so that it will be cloned properly
147   delete_button.setAttribute('onclick', "<%$pre%>deleteRow(this.rownum);");
148   var delete_cell = document.createElement('TD');
149   delete_cell.appendChild(delete_button);
150   delete_cell.appendChild(magic); // it has to go somewhere
151   <%$pre%>template.appendChild(delete_cell);
152
153   // preload rows
154   var rows = <% to_json(\@rows) %>;
155   for (var i = 0; i < rows.length; i++) {
156     <%$pre%>addRow(rows[i]);
157   }
158
159   <%$pre%>addRow();
160 }
161
162 <%$pre%>init();
163 </script>
164 <%init>
165 my %opt = @_;
166 my $pre = '';
167 $pre = $opt{'table'} . '_' if $opt{'table'};
168 my $template_row = $opt{'template_row'}
169   or die "auto-table requires template_row\n"; # a DOM id
170
171 # rows that we will preload, as hashrefs of name => value
172 my @rows = @{ $opt{'data'} || [] };
173 foreach (@rows) {
174   # allow an array of FS::Record objects to be passed
175   if ( blessed($_) and $_->isa('FS::Record') ) {
176     $_ = $_->hashref;
177   }
178 }
179 my $fieldorder = $opt{'fieldorder'} || [];
180 </%init>