enable CardFortress in test database, #71513
[freeside.git] / httemplate / elements / auto-table.html
index 89d6eac..5118b91 100644 (file)
 <%doc>
-
-Example:
-<% include('/elements/auto-table.html',
-
-              ###
-              # required
-              ###
-
-              'header'        => [ '#',  'Item', 'Amount' ],
-              'fields'        => [ 'id', 'name', 'amount' ],
-
-              ###
-              # highly recommended
-              ###
-
-              'size'          => [ 4, 12, 8 ],
-              'maxl'          => [ 4, 12, 8 ],
-              'align'         => [ 'right', 'left', 'right' ],
-
-              ###
-              # optional
-              ###
-
-              'data'          => [ [ 1,  'Widget',      25 ], 
-                                   [ 12, 'Super Widget, 7  ] ],
-              #or
-              'records'       => [ qsearch('item', { } ) ],
-              # or any other array of FS::Record objects
-
-              'prefix'        => 'mytable_',
-) %>
-
-Values will be passed through as "mytable_id1", etc.
+(within a form)
+<table>
+<tr>
+  <th>Field 1</th>
+  <th>Field 2</th>
+</tr>
+<tr id="mytemplate">
+  <td><input type="text" name="field1"></td>
+  <td><select name="field2">...</td>
+  ...
+</tr>
+</table>
+<& /elements/auto-table.html,
+  table => 'mytable',
+  template_row = 'mytemplate',
+  rows => [
+            { field1 => 'foo', field2 => 'CA', ... },
+            { field1 => 'bar', field2 => 'TX', ... }, ...
+          ],
+&>
+
+  or if you prefer:
+...
+  fieldorder => [ 'field1', 'field2', ... ],
+  rows => [
+            [ 'foo', 'CA' ],
+            [ 'bar', 'TX' ],
+          ],
+
+In the process/ handler, something like:
+my @rows;
+my %vars = $cgi->Vars;
+for my $k ( keys %vars ) {
+  $k =~ /^${pre}magic(\d+)$/ or next;
+  my $rownum = $1;
+  # find all submitted names ending in this rownum
+  my %thisrow = 
+    map { $_ => $vars{$_} } 
+    grep /^(.*[\d])$rownum$/, keys %vars;
+  $thisrow->{num} = delete $thisrow{"${pre}magic$rownum"};
+  push @rows, $thisrow;
+}
 </%doc>
-
-<TABLE ID="<% $prefix %>AutoTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
-  <TR>
-% foreach (@header) {
-    <TH><% $_ %></TH>
-% }
-  </TR>
-% my $row = 0;
-% for ( $row = 0; $row < scalar @data; $row++ ) {
-  <TR>
-%   my $col = 0;
-%   for ( $col = 0; $col < scalar @fields; $col++ ) {
-%     my $id = $prefix . $fields[$col] . $row;
-    <TD>
-      <INPUT TYPE      = "text"
-             NAME      = "<% $id %>"
-             ID        = "<% $id %>"
-             SIZE      = <% $size[$col] %>
-             MAXLENGTH = <% $maxl[$col] %>
-             STYLE     = "text-align:<% $align[$col] %>"
-             VALUE     = "<% $data[$row][$col] %>"
-             onchange  = "possiblyAddRow();"
-      >
-    </TD>
-%   }
-    <TD>
-      <IMG SRC     = "<% "${p}images/cross.png" %>" 
-           ALT     = "X" 
-           onclick = "deleteThisRow(this);"
-           >
-    </TD>
-  </TR>
-% }
-</TABLE>
-
-<SCRIPT TYPE="text/javascript">
-  var <% $prefix %>rownum = <% $row %>;
-  var <% $prefix %>table = document.getElementById('<% $prefix %>AutoTable');
-
-  function rownum_of(obj) {
-    return (obj.parentNode.parentNode.sectionRowIndex);
+<tbody id="<%$pre%>autotable"></tbody>
+<script type="text/javascript">
+var <%$pre%>template;
+var <%$pre%>tbody;
+var <%$pre%>next_rownum;
+var <%$pre%>set_rownum;
+var <%$pre%>addRow;
+var <%$pre%>deleteRow;
+var <%$pre%>fieldorder = <% encode_json($fieldorder) %>;
+
+function <%$pre%>possiblyAddRow_factory(obj) {
+  var callback = obj.onchange;
+  return function() {
+    if ( obj.rownum == <%$pre%>tbody.lastChild.rownum ) {
+      // then this is the last row, and it's being changed, so spawn a new row
+      <%$pre%>addRow();
+    }
+    if ( callback ) {
+      callback.apply(obj);
+    }
   }
+}
 
-  function possiblyAddRow() {
-    if ( <% $prefix %>rownum == rownum_of(this) ) {
-      <% $prefix %>addRow();
+function <%$pre%>set_rownum(obj, rownum) {
+  obj.rownum = rownum;
+  if ( obj.id ) {
+    obj.id = obj.id + rownum;
+  }
+  if ( obj.getAttribute('name') ) {
+    obj.setAttribute('name', obj.getAttribute('name') + rownum);
+    // also, in this case it's a form field that will be part of the record
+    // so set up an onchange handler
+    obj.onchange = <%$pre%>possiblyAddRow_factory(obj);
+  }
+  for (var i = 0; i < obj.children.length; i++) {
+    if ( obj.children[i] instanceof Node ) {
+      <%$pre%>set_rownum(obj.children[i], rownum);
     }
   }
+}
 
-  function <% $prefix %>addRow() {
-    var row = <% $prefix %>table.insertRow(<% $prefix %>rownum + 1);
-%   my $col = 0;
-%   for( $col = 0; $col < scalar @fields; $col++ ) {
-%     my $field = $prefix.$fields[$col];
-    var <% $field %>_cell = document.createElement('TD');
-      var <% $field %>_input = document.createElement('INPUT');
-      <% $field %>_input.setAttribute('name', '<% $field %>'+<% $prefix %>rownum);
-      <% $field %>_input.setAttribute('id',   '<% $field %>'+<% $prefix %>rownum);
-      <% $field %>_input.setAttribute('type', 'text');
-      <% $field %>_input.setAttribute('size', <% $size[$col] %>);
-      <% $field %>_input.setAttribute('maxlength', <% $maxl[$col] %>);
-      <% $field %>_input.style.textAlign = '<% $align[$col] %>';
-      <% $field %>_input.onchange = possiblyAddRow;
-      <% $field %>_cell.appendChild(<% $field %>_input);
-    row.appendChild(<% $field %>_cell);
-%   }
-    var delcell = document.createElement('TD');
-      var delinput = document.createElement('IMG');
-      delinput.setAttribute('src', '<% "${p}images/cross.png" %>');
-      delinput.setAttribute('alt', 'X');
-      delinput.setAttribute('onclick', 'deleteThisRow(this);');
-      delcell.appendChild(delinput);
-    row.appendChild(delcell);
+function <%$pre%>addRow(data) {
+  // duplicate the node
+  // warning: cloneNode doesn't clone event handlers that were set through 
+  // the DOM
+  // if 'data' is an object, prepopulate the row's fields with the object's
+  // elements
+  // returns the rownum of the new row
+  var row = <%$pre%>template.cloneNode(true);
+  <%$pre%>tbody.appendChild(row);
+  var this_rownum = <%$pre%>next_rownum;
+  <%$pre%>set_rownum(row, this_rownum);
+  if(data instanceof Array) {
+    for (i = 0; i < data.length && i < <%$pre%>fieldorder.length; i++) {
+      var el = document.getElementsByName(<%$pre |js_string%> +
+                                          <%$pre%>fieldorder[i] +
+                                          this_rownum)[0];
+      if (el) {
+        if ( el.tagName.toLowerCase() == 'span' ) {
+          el.innerHTML = data[i];
+        } else if ( el.type == 'checkbox' ) {
+          el.checked = (el.value == data[i]);
+        } else {
+          el.value = data[i];
+        }
+      }
+    }
+  } else if (data instanceof Object) {
+    for (var field in data) {
+      var el = document.getElementsByName(<%$pre |js_string%> +
+                                          field +
+                                          this_rownum)[0];
+      if (el) {
+        if ( el.tagName.toLowerCase() == 'span' ) {
+          el.innerHTML = data[field];
+        } else if ( el.type == 'checkbox' ) {
+          el.checked = (el.value == data[field]);
+        } else {
+          el.value = data[field];
+        }
+      }
+    }
+  } // else nothing
+  <%$pre%>next_rownum++;
+  return this_rownum;
+}
 
-    <% $prefix %>rownum++;
+function <%$pre%>deleteRow(rownum) {
+  if ( rownum == <%$pre%>tbody.lastChild.rownum ) {
+    // if this is the last row, spawn another one after it
+    <%$pre%>addRow();
   }
+  var r = document.getElementById('<%$pre%>row' + rownum);
+  <%$pre%>tbody.removeChild(r);
+}
 
-  function deleteThisRow(obj) {
-    if(<% $prefix %>rownum == rownum_of(obj))  {
-      <% $prefix %>addRow();
+function <%$pre%>set_prefix(obj) {
+  if ( obj.id ) {
+    obj.id = <%$pre |js_string%> + obj.id;
+  }
+  if ( obj.getAttribute('name') ) {
+    obj.setAttribute('name', <%$pre |js_string%> + obj.getAttribute('name'));
+  }
+  for (var i = 0; i < obj.children.length; i++) {
+    if ( obj.children[i] instanceof Node ) {
+      <%$pre%>set_prefix(obj.children[i]);
     }
-    <% $prefix %>table.deleteRow(rownum_of(obj));
-    <% $prefix %>rownum--;
-    return(false);
+  }
+}
+
+function <%$pre%>init() {
+  <%$pre%>template = document.getElementById(<% $template_row |js_string%>);
+  <%$pre%>tbody = document.getElementById('<%$pre%>autotable');
+  <%$pre%>next_rownum = <%$pre%>template.sectionRowIndex;
+  // detach the template row
+  var table = <%$pre%>template.parentNode;
+  table.removeChild(<%$pre%>template);
+  // give it an id
+  <%$pre%>template.id = 'row';
+  // prefix the ids and names of the TR object and all its descendants
+  <%$pre%>set_prefix(<%$pre%>template);
+  // add a magic identifier so we know it's been submitted
+  var magic = document.createElement('INPUT');
+  magic.setAttribute('type', 'hidden');
+  magic.setAttribute('name', '<%$pre%>magic');
+  magic.value = '1';
+  // and a delete button
+%# should this be enclosed in an actual <button> for aesthetics?
+  var delete_button = document.createElement('IMG');
+  delete_button.id = '<%$pre%>delete_button';
+  delete_button.src = '<%$fsurl%>images/cross.png';
+  delete_button.alt = 'X';
+  // use an inline string for this so that it will be cloned properly
+  delete_button.setAttribute('onclick', "<%$pre%>deleteRow(this.rownum);");
+  // and an error display
+  var error_span = document.createElement('SPAN');
+  error_span.className = 'error';
+  error_span.style.color = '#FF0000';
+  error_span.setAttribute('name', '<%$pre%>error');
+  error_span.style.padding = '5px';
+  var delete_cell = document.createElement('TD');
+  delete_cell.style.textAlign = 'left';
+  delete_cell.appendChild(delete_button);
+  delete_cell.appendChild(magic); // it has to go somewhere
+  delete_cell.appendChild(error_span);
+  <%$pre%>template.appendChild(delete_cell);
+
+  // preload rows
+  var rows = <% encode_json(\@rows) %>;
+  for (var i = 0; i < rows.length; i++) {
+    <%$pre%>addRow(rows[i]);
   }
 
-  <% $prefix %>addRow();
-</SCRIPT>
+  <%$pre%>addRow();
+}
 
+<%$pre%>init();
+</script>
 <%init>
 my %opt = @_;
-
-my @header = @{ $opt{'header'} };
-my @fields = @{ $opt{'fields'} };
-my @data = ();
-if($opt{'data'}) {
-  @data = @{ $opt{'data'} };
-}
-elsif($opt{'records'}) {
-  foreach my $rec (@{ $opt{'records'} }) {
-    push @data, [ map { $rec->getfield($_) } @fields ];
+my $pre = '';
+$pre = $opt{'table'} . '_' if $opt{'table'};
+my $template_row = $opt{'template_row'}
+  or die "auto-table requires template_row\n"; # a DOM id
+
+# rows that we will preload, as hashrefs of name => value
+my @rows = @{ $opt{'data'} || [] };
+foreach (@rows) {
+  # allow an array of FS::Record objects to be passed
+  if ( blessed($_) and $_->isa('FS::Record') ) {
+    $_ = $_->hashref;
   }
 }
-# else @data = ();
-
-my $prefix = $opt{'prefix'};
-my @size = $opt{'size'} ? @{ $opt{'size'} } : (map {16} @fields);
-my @maxl = $opt{'maxl'} ? @{ $opt{'maxl'} } : @size;
-my @align = $opt{'align'} ? @{ $opt{'align'} } : (map {'right'} @fields);
-
+my $fieldorder = $opt{'fieldorder'} || [];
 </%init>