SASS Variable for Image Path

I'm building out a new site in Statamic which is my go to CMS for smaller sites that don't all the power that ExpressionEngine or CraftCMS have to offer. With Statamic your images, css, and javascript files are required to be in specific folders. This is not an issue except that I'm a lazy typer.

In my sites I usually keep images in a folder at root so that all I need to type is /images/file_name.jpg and it works. However images are required to be in /_themes/theme_name/img/ which is a whole lot longer to type and I know I'll have typos. My solution was to set a variable 

1
$imgpath: /_themes/etf/img;

And then in the relevant css have this:

1
2
3
header {
	background: url($imgpath/hero-image.jpg) no-repeat left bottom;
}

That however resulted in errors.

1
Error: Invalid CSS after \"...path/hero-image\": expected \")\", was \".jpg) no-repeat...\

The solution is to code the variable slightly different when putting it in a path by adding curly braces around it and a proceeding pound sign like this:

1
2
3
header {
	background: url(#{$imgpath}/hero-image.jpg) no-repeat left bottom;
}

Much easier to write out and remember than the full path to the image folder.

ExpressionEngine, Structure and Bootstrap Navbar

Two agencies that I regularly do ExpressionEngine work require that the Structure Module is used in the builds. I've become quite proficient with Structure, but still prefer to build sites without it and do so when I have the option.

One of the issues I have with Structure is that the navigation tag is difficult to work with especially if you are using a framework such as Foundation or Bootstrap when building your sites. The structure nav tag doesn't allow for the customization of the HTML that is needed when using Bootstrap or Foundation. What I've done in the past is hard code the main nav items and use multiple Structure nav tags for the dropdowns. It works but isn't very efficient.

By adding a little javascript I can now use a single structure nav tag, have working dropdowns and allow the client to reorder the navigation at will.

My Navbar HTML looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div class="navbar-wrapper">
  <div class="container">
    <div class="navbar navbar-static-top" role="navigation">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="/">Site Name</a>
        </div>
        <div class="navbar-collapse collapse">  
          {exp:structure:nav 
            start_from ="/" 
            current_class="active" 
            max_depth="3"
            show_depth="3"
            css_class="nav navbar-nav navbar-right"
          }
        </div>
      </div>
    </div>
  </div>
</div>

All we need is a little jquery to get this to work correctly.

1
2
3
4
5
6
7
8
// make structure play nice with bootstrap dropdown navs
$( document ).ready(function() {
	//add dropdown class if li contains ul and data-toggle to link
	$('.navbar ul.navbar-nav li:has(ul)').addClass('dropdown').find('> a').addClass('dropdown-toggle').attr('data-toggle', 'dropdown');
 
	//add dropdown-menu to nested ul
	$('.navbar ul.navbar-nav li > ul').addClass('dropdown-menu');
});

Migrating Content from ExpressionEngine to Craft

I've wanted to give Craft CMS a go for a while now and figured the best way to do it is with a personal project. My photo blog is in dire need or a revamp and as such I've decided to migrate to Craft. Installing Craft is dead easy and took only about 90 seconds including time to download the zip file.

Before I started working on the design I wanted to be sure that I would be able to easily migrate four years of content so the first step was migrating my Entries. This turned out to be relatively painless with the use of this import plugin.

I first copied all photos from the live site to my upload folder of the new site. Then in the Craft CP click the gear icon on the top right, then Assets, New source and fill in the fields. both the path and url can be set to relative paths, but be sure to not use a leading slash. After saving click the gear icon again and go to the lower left and update asset indexes. A short time later all your images are in the database. You can confirm this by clicking on Assets in the nav.

The next step is to install the Import plugin by putting the files in /craft/plugins then in the CP click gear and plugins and finally install. This plugin only imports content in CSV format. This requires a new template in ExpressionEngine which will need to be set wiht php turned on in order to force the browser to download the file directly.

The php at the top of the template tells the browser this is an excel file and forces it to download the file on browser load

1
2
3
4
5
6
<?php
    header("Content-type: application/vnd.ms-excel");
header("Content-Disposition: attachment; filename=photoblog-export.xls");
header("Pragma: no-cache");
header("Expires: 0");
?>

The next bit of code contains all our fields and how we want them named. This is important to easily map the fields using the import plugin. Line 20 has entry_date with extra spaces this is due to my blog rendering it and not displaying the code correctly - remove extra spaces for it to work. When migrating images, be sure to only include the image name. My images currently reside in a Matrix field {photos} and a file cell. In order to get just the file name it is necessary to use the file tag pair like this {image}{file_name}{/image} where the outer tag pair is the file field/cell name.

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<table>
<tbody>
<tr>
   <td>Title</td>
    <td>Url Title</td>
    <td>Entry Date</td>
    <td>Description</td>
    <td>Image</td>
</tr>
{exp:channel:entries channel='photoblog' limit='500' disable="categories|member_data|pagination|comments" status="not foo"}
    <tr>
            <td>Migrating Content from ExpressionEngine to Craft</td>
            <td>{url_title}</td>
            <td>{ entry_date }</td>
            <td>{description}</td>
            <td>{photos}{image}{file_name}{/image}{/photos}</td>
    </tr>
{/exp:channel:entries}
</tbody>

After downloading the file open it in Excel, you may see a warning that the file format doesn't match the extension, open it anyhow. Now do a save as and choose CSV (comma delimited). My research indicated that this extra step is necessary as using the template to save as CSV with content that contains quotes may not work correctly.

Now to import, click Import in the Nav. Fill in the fields as required and upload your CSV file.

Go to the next page where we map the fields to Craft fields. 

Now hit import, wait a few minutes and all of your entries will be imported. To confirm click on "Entries" in the top nav.  Assuming that new content is added to the live site before this build is finished then all you need to do to update is do a new export with only the new entries since the last import and repeat the process. Super easy.

Now it's time to start templating the new site.

Comment Update

Joe sent me an email in response to his my comment below - commenting is turned off automatically after a couple of weeks for spam protection. I've copied his comment here:

 Hi, just replying to my comment on your article: Migrating Content from
ExpressionEngine to Craft. I couldn't make another comment on the article
for some reason.

Anyway, I was able to import my images finally. I actually used the Feed Me
plugin (https://github.com/engram-design/FeedMe) Very similar to Import, but
it allows for XML which is super nice. And to get the images from EE Matrix
to map to Craft Assets field, I just had to separate them with a comma in
the XML.  That part of the EE template looked like this:

1
2
3
4
<images>
<![CDATA[{project-images
backspace="1"}{image}{file_name},{/image}{/project-images}]]>
</images>

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.

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:

1
2
3
4
{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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{exp:switchee variable="{pre_seg_6}" parse="inward"} 
{case value="#^P(\d+)$#|''" default="Yes"}
	{exp:channel:entries channel="tool" url_title="{pre_seg_4}" entry_id="{pre_entry_id}" status="not foo"}
		{exp:channel:form 
			channel="tool"
			include_jquery="no"
			include_assets="yes"
			return="tool/page/6/URL_TITLE/ENTRY_ID"
			status="pending"
			url_title="{pre_seg_4}"
			entry_id="{pre_entry_id}"
			author_id="2"
			logged_out_member_id="2" 
			require_entry="yes"
			}
			
			<div class="fields" id="sortable">
				{tool_experience2}
				<div class="field">
					VARIOUS FIELDS
				</div>
				{/tool_experience2}
			</div>
			<div class="buttons">
				<a href="/tool/page/5/{pre_seg_4}/{pre_entry_id}/add" class="btn btn-primary">Add Experience</a>
			</div>
			{exp:low_variables:single var="lv-paging"}
		{/exp:channel:form}
	{exp:channel:entries}
{/case}
{case value="add"}
	<h3>Add Another Work Experience</h3>
	{exp:channel:form 
			channel="tool"
			include_jquery="no"
			include_assets="yes"
			return="tool/page/5/URL_TITLE/ENTRY_ID"
			status="pending"
			url_title="{pre_seg_4}"
			entry_id="{pre_entry_id}"
			author_id="2"
			logged_out_member_id="2"
			require_entry="yes"
			}
		<div class="fields" id="sortable">
			<div class="field">
				VARIOUS FIELDS
			</div>
		</div>
			<button type="submit" class="btn btn-default">Save Changes</button>
	{/exp:channel:form}
{/case}
{/exp:switchee}

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:

1
2
3
4
<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.

1
2
3
4
5
6
7
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<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:

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

Replace with This

3257
3258
3259
3260
3261
3262
$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.

Your Voice Here
Leave a Comment