9 <td><input type="text" name="field1"></td>
10 <td><select name="field2">...</td>
14 <& /elements/auto-table.html,
16 template_row = 'mytemplate',
18 { field1 => 'foo', field2 => 'CA', ... },
19 { field1 => 'bar', field2 => 'TX', ... }, ...
25 fieldorder => [ 'field1', 'field2', ... ],
31 In the process/ handler, something like:
33 my %vars = $cgi->Vars;
34 for my $k ( keys %vars ) {
35 $k =~ /^${pre}magic(\d+)$/ or next;
37 # find all submitted names ending in this rownum
39 map { $_ => $vars{$_} }
40 grep /^(.*[\d])$rownum$/, keys %vars;
41 $thisrow->{num} = delete $thisrow{"${pre}magic$rownum"};
45 <tbody id="<%$pre%>autotable"></tbody>
46 <script type="text/javascript">
49 var <%$pre%>next_rownum;
50 var <%$pre%>set_rownum;
52 var <%$pre%>deleteRow;
53 var <%$pre%>fieldorder = <% encode_json($fieldorder) %>;
55 function <%$pre%>possiblyAddRow_factory(obj) {
56 var callback = obj.onchange;
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
68 function <%$pre%>set_rownum(obj, rownum) {
71 obj.id = obj.id + rownum;
73 if ( obj.getAttribute('name') ) {
74 obj.setAttribute('name', obj.getAttribute('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);
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);
86 function <%$pre%>addRow(data) {
88 // warning: cloneNode doesn't clone event handlers that were set through
90 // if 'data' is an object, prepopulate the row's fields with the object's
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 |js_string%> +
100 <%$pre%>fieldorder[i] +
103 if ( el.tagName.toLowerCase() == 'span' ) {
104 el.innerHTML = data[i];
105 } else if ( el.type == 'checkbox' ) {
106 el.checked = (el.value == data[i]);
112 } else if (data instanceof Object) {
113 for (var field in data) {
114 var el = document.getElementsByName(<%$pre |js_string%> +
118 if ( el.tagName.toLowerCase() == 'span' ) {
119 el.innerHTML = data[field];
120 } else if ( el.type == 'checkbox' ) {
121 el.checked = (el.value == data[field]);
123 el.value = data[field];
128 <%$pre%>next_rownum++;
132 function <%$pre%>deleteRow(rownum) {
133 if ( rownum == <%$pre%>tbody.lastChild.rownum ) {
134 // if this is the last row, spawn another one after it
137 var r = document.getElementById('<%$pre%>row' + rownum);
138 <%$pre%>tbody.removeChild(r);
141 function <%$pre%>set_prefix(obj) {
143 obj.id = <%$pre |js_string%> + obj.id;
145 if ( obj.getAttribute('name') ) {
146 obj.setAttribute('name', <%$pre |js_string%> + obj.getAttribute('name'));
148 for (var i = 0; i < obj.children.length; i++) {
149 if ( obj.children[i] instanceof Node ) {
150 <%$pre%>set_prefix(obj.children[i]);
155 function <%$pre%>init() {
156 <%$pre%>template = document.getElementById(<% $template_row |js_string%>);
157 <%$pre%>tbody = document.getElementById('<%$pre%>autotable');
158 <%$pre%>next_rownum = <%$pre%>template.sectionRowIndex;
159 // detach the template row
160 var table = <%$pre%>template.parentNode;
161 table.removeChild(<%$pre%>template);
163 <%$pre%>template.id = 'row';
164 // prefix the ids and names of the TR object and all its descendants
165 <%$pre%>set_prefix(<%$pre%>template);
166 // add a magic identifier so we know it's been submitted
167 var magic = document.createElement('INPUT');
168 magic.setAttribute('type', 'hidden');
169 magic.setAttribute('name', '<%$pre%>magic');
171 // and a delete button
172 %# should this be enclosed in an actual <button> for aesthetics?
173 var delete_button = document.createElement('IMG');
174 delete_button.id = '<%$pre%>delete_button';
175 delete_button.src = '<%$fsurl%>images/cross.png';
176 delete_button.alt = 'X';
177 // use an inline string for this so that it will be cloned properly
178 delete_button.setAttribute('onclick', "<%$pre%>deleteRow(this.rownum);");
179 // and an error display
180 var error_span = document.createElement('SPAN');
181 error_span.className = 'error';
182 error_span.style.color = '#FF0000';
183 error_span.setAttribute('name', '<%$pre%>error');
184 error_span.style.padding = '5px';
185 var delete_cell = document.createElement('TD');
186 delete_cell.style.textAlign = 'left';
187 delete_cell.appendChild(delete_button);
188 delete_cell.appendChild(magic); // it has to go somewhere
189 delete_cell.appendChild(error_span);
190 <%$pre%>template.appendChild(delete_cell);
193 var rows = <% encode_json(\@rows) %>;
194 for (var i = 0; i < rows.length; i++) {
195 <%$pre%>addRow(rows[i]);
206 $pre = $opt{'table'} . '_' if $opt{'table'};
207 my $template_row = $opt{'template_row'}
208 or die "auto-table requires template_row\n"; # a DOM id
210 # rows that we will preload, as hashrefs of name => value
211 my @rows = @{ $opt{'data'} || [] };
213 # allow an array of FS::Record objects to be passed
214 if ( blessed($_) and $_->isa('FS::Record') ) {
218 my $fieldorder = $opt{'fieldorder'} || [];