Skip to Main Content

Channel Form and Grid/Matrix fields

I was approached to build a site that is essentially a resume builder; Visitors to the site would visit and fill in a multi page form providing the details to build out their resume. I was positive that this wouldn't be an issue with ExpressionEngine as Channel Form makes it easy to create front end forms and Grid is a first party field that would allow me to have groups of fields auto duplicated for sections such as Job Experience or Education. However this turned out to not be the 100% correct.

I was approached to build a site that is essentially a resume builder; Visitors to the site would visit and fill in a multi page form providing the details to build out their resume. I was positive that this wouldn't be an issue with ExpressionEngine as Channel Form makes it easy to create front end forms and Grid is a first party field that would allow me to have groups of fields auto duplicated for sections such as Job Experience or Education. However this turned out to not be the 100% correct.

Grid does work with Channel form but it doesn't allow for customization on the front end. You can only display a grid field by using the custom_fields tag pair like this:

    
      {custom_fields}
	{field_instructions}
	{display_field}
{/custom_fields}
    
  

The issue with this is immediately clear in that you cannot control the layout or design of your fields. Also with a responsive site and a seven field grid, the grid is going to break out of the site as it is output as one long table row that is not responsive. Matrix stays in it's container but there is no documented way to display customly. In the screenshot below, The matrix cells and grid cells have the same width. The only difference is matrix allows the use of P&T switch rather than checkboxes

The final code will allow a custom layout like this which will rearrange itself based on the viewport size due to responsive coding.

Meta Q wrote a post in March 2013, Bring Matrix Data Editing to the Front-End, that addressed this issue using Matrix and Safecracker. Safecracker was renamed to Channel form and has had some changes so I proceeeded to use this post as a guide to do what was needed.

Using that article I got everything set up, but it would only display existing data and would not save new fields. After some frustration and help from @Lincolnpixel, who is currently building a similar project I was able to get everything working. The key to success is to not have new fields appear immediately below existing content as in the publish page, but rather to load a new page with new fields and on save go back to the editing page. To do this I used switchee and kept everything in the same template.

    
      {custom_fields}
	{field_instructions}
	{display_field}
{/custom_fields}
    
  

As you can see from the above code the first case displays the matrix field for editing based on the url_title and entry_id being passed through a low variable (see my blog post on how I use Low Variables) using preparse. Additionionally it is required to wrap the channel:form tag with channel:entries in order to display saved data. When clicking the "Add Experience" button it goes to the same page but adds a new segment /add/ which then loads up the page for adding new content to the matrix field. There is no need for a channel:entrires on the "add" page as we are creating new data.

To display the content of each matrix row correctly requires poking around the database and getting the column_id for each cell in the Matrix as you are unable to use the cell_name to call the data. You can find the column ids by opening your database and then going to the exp_matrix_cols table and looking at the first column - match that up with the field name in col_name and add your fields like this replacing "x" with the correct id:

    
      <div class="form-group">
	<label for="tool_experience2[row_id_{row_id}][col_id_x]">Your Label</label>
	<input type="text" class="form-control" name="tool_experience2[row_id_{row_id}][col_id_x]" value="{company}">
</div>
    
  

On the "add" page the fields need to be called using the same formula above, however since we are creating new rows instead of
row_id_{row_id} we use row_new_0. Additionally there are two new hidden fields required.

    
      <input class="new_row_id" type="hidden" name="field_name[row_order][]" value="row_new_0">
<input type="hidden" name="title" id="title" value="{pre_seg_4}">
 
<div class="form-group">
	<label for="company[row_new_0][col_id_1]">Your Label</label>
	<input type="text" class="form-control" name="field_name[row_new_0][col_id_1]" >
</div>
    
  

In the above code replace field_name with the name of your field. The first hidden field creates a new row and saves it when we submit the form with the help of the javascript from the Meta Q article (will post further down). The second hidden field using the url_title to ensure that data is added to the correct entry. In my case this is in segment_4 and is being passed through low_variables as {pre_seg_4}.

I found that date fields using the native date field type were not working as expected so I used plain field types and used jQuery UI datepicker to. To do this you will need to get a custom jQuery UI theme if you don't want your datepickers to look like the publish page datepickers. Also the regular datepicker shows days and in a resume the date is usually unecessary so my code set's it to pick only the month and year.

HTML

    
      <div class="row">
	<div class="col-sm-6">
		<div class="form-group">
			<label for="date_from[row_new_0][col_id_4]">Date From</label>
			<input type="text" class="form-control datepicker" name="tool_experience2[row_new_0][col_id_4]">
		</div>
	</div><!--6 cols-->
 
	<div class="col-sm-6">
		<div class="form-group">
			<label for="date_to[row_new_0][col_id_5]">Date To</label>
			<input type="text" class="form-control datepicker" name="tool_experience2[row_new_0][col_id_5]">
		</div>
	</div><!--6 cols-->
</div><!--row-->
    
  

JS/CSS

    
      <script>
$(function() {
$( ".datepicker" ).datepicker({
	changeMonth: true,
  	changeYear: true,
  	yearRange: "1970:2020",
  	dateFormat: 'MM, yy',
  	maxDate: "0D",
  	minDate: "-50Y",
  	showButtonPanel: true,
  	onClose: function(dateText, inst) { 
        var month = $("#ui-datepicker-div .ui-datepicker-month :selected").val();
        var year = $("#ui-datepicker-div .ui-datepicker-year :selected").val();
        $(this).datepicker('setDate', new Date(year, month, 1));
    }
});
});
</script>
 
<style>
/* hide calendar and display only month/year*/
.ui-datepicker-calendar {
    display: none;
    }
</style>
    
  

The Javascript code, unchanged from the Meta Q article, that creates the new row is here. Lines 18-32 are probably unecessary as we are no long creating new rows on the edit page but rather creating new rows on separate page due to that not working at all.

    
      <script type="text/javascript">
$(function() {
    $( "#sortable" ).sortable({
     containment: "parent"
    });
});
$(document).ready(function(){
 
$('.fields').on('click', '.delete_row', function(e){
 
  e.preventDefault();
  $(this).parent().remove();
  if ($('.fields div').length < 1){
   $('#new').val('create first row');
  }
});
 
var $clone = $(".fields div:eq(0)").clone().html();
var $button_txt = $('#new').val();
count = 1;
$('#new').on('click', function(e){
  e.preventDefault();
  if ($('#new').val() != $button_txt){
   $('#new').val($button_txt);
  }
  var $counter = count++;
  var $new_row = $clone.replace(/row_new_0/g,'row_new_'+$counter);
 
  $(".fields").append('<div class="field">'+$new_row+'</div>');
});
 
});
 
</script>
    
  

Bug Fix

There is one bug fix that will need to be applied to your ExpressionEngine install if you have more than one matrix field in the same channel. This needs to be applied whether both matrix fields are on the same page (on the front end) or not. If you only have one matrix field then it is not necessary.

File: system/expressionengine/modules/channel/libraries/channel_form/Channel_form_lib.php as of EE 2.9.2 line 3257

Method: replace_tag

Find This:

    
      ee()->api_channel_fields->apply('_init', array(array(
    'row' => $this->entry,
    'content_id' => $this->entry('entry_id')
)));
    
  

Replace with This

    
      $field_id = $this->get_field_id($field_name);
ee()->api_channel_fields->apply('_init', array(array(
    'row' => $this->entry,
    'content_id' => $this->entry('entry_id'),
    'field_id' => $field_id
)));
    
  

Final Thoughts

We now able to view/edit matrix rows as well as add a new row as necessary. Adding a new row is not as elegant as in the backend due to having to load a new page. However that loss of elegance is more than mitigated by the fact that we can now display all our matrix cells as we want them to look and are not limited to the tabular layout/look of the Control Panel matrix/grid.

You could likely use a modified version of this with the first party grid field, but there would be need to experiment with how content is added and checking the database. Until EllisLab adds additional tags to allow customized display of grid fields, the best approach in my opinion is using Matrix with the above code.

My full template with all seven matrix cells is here.

Update March 12, 2015

Andrew Armitage has written a post on how to do this with the native grid field type.