Bring Matrix Data Editing to the Front-End

Mike Wenger / Posted 3.7.2013

Bring Matrix Data Editing to the Front-End


Two of my favorite aspects of using ExpressionEngine are Matrix and Safecracker. In terms of making life easier, they're amazing.

For anyone unfamiliar with it, Safecracker is a fantastic stand-alone entry form - it enables entering entries into EE from the front-end. If you haven't had the opportunity to explore Safecraker, I highly recommend doing so.

In addition to Safecracker, I'm also a huge fan of Pixel and Tonic's Matrix.  As an add-on, Matrix is one of the most timesaving  (and headache-saving) tools to have at your disposal.

Have complex data that you would love to have all in one field? Matrix is the key. It's super easy to output all your data on the front-end as well with its handy tag pairs — it just makes so many tasks a breeze.

But what if you want to be able to enter and edit on the front-end?

I'll note that you can use Matrix's {field:field_name} method and that will output a table Matrix like in the Control Panel, but it may not provide what you're looking for in terms of look.

There’s a more creative way to do all this, and with a little more flexibility.

Submitting rows to a Matrix: An entry perspective

Understanding how Matrix rows are added is key to understanding how to edit then.

Assuming you have a two column Matrix, you'll have three inputs to manage, and code that looks like this:

{exp:safecracker channel="your_channel" return="/URL_TITLE"}
<input type="hidden" name="title" id="title" value="{current_time format='%M %j%S %Y'}" maxlength="100" onkeyup="liveUrlTitle();">
<input type="hidden" name="url_title" id="url_title" value="">
<input type="hidden" name="entry_date" id="entry_date" value="{entry_date}">

<div class="fields" id="sortable">
 
  <div class="field"><!--THIS IS A MATRIX ROW-->
   <input class="new_row_id" type="hidden" name="field_name[row_order][]" value="row_new_0">
   <input type="text" name="field_name[row_new_0][col_id_1]" value="{col_field_name_1}">
   <input type="text" name="field_name[row_new_0][col_id_2]" value="{col_field_name_2}">
   <span class="delete_row">[x]</span>
  </div>
  
</div>
<div class="buttons">
  <input id="new" type="button" value="add row">       
  <input type="submit" value="Submit">
</div>
{/exp:safecracker}

You'll want to replace “field_name” and “col_field_name_x” with your field names, and “col_id_x” with your column IDs. If you need to find your row IDs, you can either look in the database, or use the {field:field_name} tag and look at what it outputs.

The first input sets the row as a new row, while the following rows are your column data inputs. If we just wanted to create 3 rows of static input, we would duplicate the div “field” two more times and change “row_new_0” in the first input to increase by one. For example, the second iteration of the div would be “row_new_1,” and the third would be “row_new_2.” Since we will be mimicking the CP function, we'll want an “add row” function attached to the button with ID “new.”

It’s important to understand that “row_new_x” does NOT indicate the row_id – it is an identifier to separate it from all other rows that you will be adding. The row ID will be designated from the count in the database.

Next we’re going to add the following to our file:

<script src="http://code.jquery.com/jquery-1.8.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.1/jquery-ui.js"></script>
<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>

What we're doing is when clicking the “add row” button, a clone of div.field has been created, and all “row_new_x” values are being replaced by the counter integer. This way, when we add, reorder or delete we will not have a duplicate number — and therefore no lost data.

I've also added a delete row function and text change for the “row” button, so if all rows are deleted, the user is prompted to create a “first” one. Since we're using drag-and-drop for sorting, it's also good to note that the order in which the rows will be saved is based on the HTML DOM order.

There it is: simple with a hint of complexity.

Change existing Matrix rows for an entry: An editing perspective

Editing from an entry detail view is similar to the way in which we normally output Matrix data. It would look something like this:

{exp:safecracker channel="recipes" return="/URL_TITLE" url_title="{segment_1}"}
<input type="hidden" name="title" id="title" value="{title}" size="50" maxlength="100" onkeyup="liveUrlTitle();">
<input type="hidden" name="url_title" id="url_title" value="{url_title}" maxlength="75" size="50">
<input type="hidden" name="entry_date" id="entry_date" value="{entry_date}" maxlength="23" size="25">

<div class="fields" id="sortable">

  {recipes_ingredients}
  <div class="field"><!--THIS IS A MATRIX ROW-->
   <input type="hidden" name="field_name[row_order][]" value="row_id_{row_id}" />
   <input type="text" id="field_name" name="field_name[row_id_{row_id}][col_id_1]" value="{col_field_name_1}">
   <input type="text" id="field_name" name="field_name[row_id_{row_id}][col_id_2]" value="{col_field_name_2}">
   <input type="checkbox" name="field_name[deleted_rows][]" value="row_id_{row_id}">delete
  </div>
  {/recipes_ingredients}

</div>
<div class="buttons">
   <input type="submit" value="Save">
</div>
{/exp:safecracker}

Our row identifiers are now defined by the row ID. Again, we can sort via jQuery and the rows will be saved in the HTML DOM order in which you have arranged them. The input with name “field_name[deleted_rows][]” will delete the row if checked, then submitted. You could also use Ajax to submit the form and delete– be creative!

Now you can utilize two of the most useful (not to mention just plain cool) ExpressionEngine modules/add-ons together to make magic happen. Let your imagination run wild and find creative uses for using Matrix on the front-end through Safecracker.

Mike Wenger

Mike Wenger

Front-end designer and developer at Q Digital Studio

Mike Wenger is a front-end designer and developer at Q Digital Studio. Mike's design roots run deep – he's got a degree in Visual Communications from the University of Dayton, and experience with both print and web design. He's all about pushing the boundaries of thought process and finding new avenues for artistic growth. He loves both the analytic and creative side of front-end design; anytime he can use both is perfect for him.

Mike is a self-proclaimed outdoors fanatic and enjoys basically anything outdoorsy. This includes (but is not limited to) kayaking, cycling, backpacking, hiking, snow sports, and scuba diving. He and his wife, Nicole, recently moved from Ohio, and love the laid-back lifestyle Colorado has to offer. Along with their two dogs, Chloe and Summit, Mike and Nicole are enjoying life and living at a nice pace.

If Mike had to describe himself in 140 characters, he would do so thusly: "One word, passion. I live, work and breathe it. I live every day to the fullest, do for a living what I enjoy most, and love every moment."