How I built a Tumblr-style blog with ExpressionEngine

Contents

  1. Setting expectations
  2. Create a channel for every type of post
  3. Add-ons that I use
  4. Use a single Field Group for all channels
  5. The templates to display your posts
  6. Building your archives pages
  7. Custom styles for different types of posts
  8. Create custom publish page layouts
  9. Create bookmarklets

I've always been a fan of the experience of Tumblr; as soon as tried it, I knew that I wanted to do something like it for my own site running ExpressionEngine 2.

This very good article by Andy Johnson from a few months ago covers a lot of the basic ideas. I have some additional thoughts based on my experiences trying to doing very close to the same thing.

For experienced EE developers, a lot of this will be nothing groundbreaking, but if you're relatively new to EE you might find this interesting.

Setting expectations

First, I wouldn't try very hard to perfectly duplicate what Tumblr does. As Khoi Vinh points out, Tumblr has effectively made blogging "as absurdly simple as it can possibly be made." We're not going to be able to match their interface or user experience click-for-click.

But, hey, maybe you want to build something of your own or maybe you want to manage your own content. Let's get started.

Create a channel for every type of post

As Andy points out, to best re-create the Tumblr experience of click-this-button-to-share-this-type-of-thing, you should make a channel for every type of post that you want. Here are the channels that I have:

  • images
  • link
  • movie
  • music
  • quote
  • text

I have a few others specific to my site (articles, projects, and posts pulled from Twitter) that I won't be covering here.

I would use a solid descriptive name for each channel, because the channel name is what will show up on the CP interface.How channels appear in the CP


Also, for the short name of each channel, make it something simple, clear, and accurate that you'd be comfortable exposing in a URL. You'll see why later.Simple names for channels preferred here

Channel preferences

Some of the default settings for EE are less than optimal, so here's what I change for each channel (Admin > Channel Administration > Channels > Edit preferences): 

  • Under the Path Settings, set all the URL fields to "/blog/" or whatever your template group will be. This includes the comment page URL, so you don't have to tack /comments/ onto your URL for single entries (the default).
  • Under Channel Posting Preferences, for the "Automatically turn URLs and email addresses into links" option, choose "No."
  • If you find that you do a lot of editing before a post goes "live," I would set the default status for entries to "Closed." (Or whatever designates a 'draft' for your statuses. I find the default Open|Closed is good enough.) 

Add-ons that I use

Here are some of the add-ons that I use to make my life easier. Many of them are not free, but all are absolutely worth the money.

  • Wygwam: By Brandon Kelly (Pixel and Tonic). It's the best WYSIWYG editor I've used in ExpressionEngine. I've found this is much simpler than hand-coding all my own HTML for entries (although I do that sometimes because I'm anal) or trusting the built-in ExpressionEngine XHTML parser. It also has a respectable interface for uploading files (like images).
  • Tag: By Solspace. If you want to use tags to organize entries, this is the most flexible and powerful add-on that I've seen.
  • Matrix: Also by Pixel and Tonic. Matrix is the premier add-on for any type of information when you find yourself saying, "How many of these am I going to have?" If you want a free alternative for very simple lists of information, Pixel and Tonic has the "List" add-on.
  • Parse URL: This can be handy for showing a portion of a URL. I use it to only show the domain when linking to an external site as in, "See the original on youtube.com."
  • Strip HTMLXHTML Typography, or Low Replace: These aren't essential, but they're useful here and there for stripping or escaping characters if you need to reuse a custom field in a place where it might break the HTML.

Use a single Field Group for all channels

Here's where I deviate from Andy's article, where he's using multiple field groups (one for each channel). I'd rather do one honkin' custom field group that has all the fields I might need.

The nice part of this is that you don't have to duplicate common fields (like a "Body" or a "Link" text field) that multiple channels might need.

The downside is that you have to do some tweaking on your publish pages, which I'll get into later.

Custom fields that I use

Here are the fields I figured I needed:

  • Body: Every channel gets this general-purpose text field. I use the Wygwam WYSIWYG editor: it makes the input pretty and I can use it as a simple file uploader for images too. I also use this field to paste in <embed> code for movies.
  • External URL: Almost all channels get this. It's a plain text input for a URL to, say, another site or a YouTube page.
  • Quote attribution: This is a plain text input for the "quote" channel only.
  • Music files: This is a Matrix field that I (eventually) will use to share MP3 files. It has sub-fields for the file, artist name, and song name. This would let me upload as many files as I wanted if I wanted to post a playlist or something.
  • Featured comments: This is another Matrix field. I don't allow open commenting on my posts, but if someone gets back to me with something interesting, I'll share it. The sub-fields are "Commenter Name", "Comment text", "Date", and "My response."

Those fields cover pretty much everything that I would want to post, with the Body field doing a lot of the work for showing images and movies.

More ideas for custom fields

If you wanted to get more specific or user-friendly, you can always add a lot more fields. That's why you're using ExpressionEngine, right? Here are some ideas that I came up with but haven't put into use personally:

  • Image(s): You could make a Matrix field with sub-fields of the image file, a caption, alt-text, and the like. This would give you a flexible way to upload 1 picture or more (if you wanted to allow for galleries or a slideshow in a single post).
  • Video-specific field: There's a very nice-looking Video Player add-on that lets you search for and access videos from a bunch of video services. That might be easier and more flexible than pasting the <embed> code into a Body field.
  • Chat: Again, because a chat could be anywhere from two to hundreds of lines, Matrix would be your friend. Your sub-fields would be Name (who's talking?) and Text (what are they saying). This way you could let Matrix and your templates handle the formatting.

Make every field optional

Because each channel will only be using a small subset of the custom fields, do not make any custom field required. (Unless you're really, really sure.)Mark fields as not required

The templates to display your posts

Just a single template (well, with an embed)

I only use one template group ("blog") and the index template in that group. If you wanted to have a separate template for posts, you might add a "post" template, so your posts could be at /blog/post/url_title. Personally, I prefer the simpler URL structure of /blog/ for the main page and /blog/url_title for individual posts, but it requires a little more logic in your templates.

Take advantage of the late parsing of embedded templates

For ease of maintenance, all the layout of my site is handled by a single embedded topic. Basically, every single template starts with this:

{embed="embeds/body"} 

Simple enough, right? Well, let's get a little crazier! Taking a hint from John D Wells' article on using a single embed template, we'll take advantage of the fact that embed tags are parsed dead last when rendering the page.

{embed="embeds/body"
   {if segment_2 == ""}
      layout_type="many_posts"
      listings_channels="images|links|movies|quotes|text|music"
      listings_limit="20"
   {/if}

   {if segment_2}
      layout_type="single_post"
   {/if}
}

So, above I'm checking for a second segment in the URL (if it's there, I want a single post) and using that condition to pass some embed variables. Yes, you can use almost any conditional to add variables to an embed tag, because the tag won't be parsed until way later in the process.

I do this with embedded variables so that if I want to re-use the basic code in other places, like my portfolio page or search results, I just pass in different values for the embed variables. For example, on my portfolio template, I have this:

{embed="embeds/body"
   {if segment_2 == ''}
      layout_type="many_posts"
      listings_channels="projects"
      listings_limit="10"
   {/if}
}

Over in the embed/body template, I use those variables in conditionals and pass them into {exp:channel:entries} tags:

{!-- Multiple entries --}
{if embed:layout_type == "many_posts"}
   {exp:channel:entries 
      channel="{embed:listings_channels}"
      limit="{embed:listings_limit}"
      status="open"
      dynamic="no"
      disable="categories|member_data|pagination"}

	{!-- CODE FOR MANY POSTS GOES HERE --}

   {/exp:channel:entries}
{/if}

{!-- Single entries --}
{if embed:layout_type == "single_post"}

   {exp:channel:entries
      limit="1"
      status="open|closed"
      dynamic="yes"
      disable="categories|member_data|pagination"}

      {!-- CODE FOR A SINGLE POST GOES HERE --}

   {/exp:channel:entries}
{/if}

Note that the channel:entries:tag has dynamic="no" on the main page, but dynamic="yes" for a single entry page. For a single entry, segment_2 will be the url_title for an entry, and the exp:channel:entries tag can limit itself to a single entry based on that.

For the single_post section, I use status="open|closed" so that I can view the content of closed entries if I navigate to them directly. I use that for checking on drafts (set to "Closed") before I switch the status over to "Open".

Use conditionals for your custom fields

I use a lot of {if custom_field} conditionals to reveal bits of code. This goes a long way to help you avoid rewriting code. For example, here's how I wrap a link tag around the title of a single post if I've entered a value in my External URL field:

<h1>
   {if custom_external_link}
      <a href="{custom_external_link}">
   {/if}

   {title}

   {if custom_external_link}
      </a>
   {/if}
</h1>

You could of course also use conditionals based on the channel, like {if channel_short_name == "images"}, as Andy's article suggests. Whatever is easiest for you.

Building your archives pages

Here we're going to build up the pages for your archives that list all the posts from a single channel. Like this one on my site for quotes.

Use your channels like you might use categories for a regular site; I think channels are more idiot-proof – you can't forget to assign a channel to an entry.

Have simple short names for your channels

This works best when channels each have a good descriptive Full Channel Name and Channel Short Name (Admin > Channel Administration > Channels > Edit preferences). So skip things like myTumblr_images when you can just use images.

(If you change the short names for any channels here, remember to check all of your exp:channel:entries tags to make sure they're still looking for the right channel names!)

Creating lists of channels (for navigation, footers, etc.)

There's no built-in {exp:channel} tag that will loop through all of your weblog channels in the same way that the {exp:channel:categories} tag can loop through all of your categories. Bummer.

But we can roll our own with the {exp:query} module:

<h1>Post types</h1>
{exp:query 
sql="SELECT channel_name, channel_title, channel_description 
FROM exp_channels 
ORDER BY total_entries ASC"}

   {if count == "1"}
      <ul>
   {/if}
	
      <li><a href="{path='archives'}/{channel_name}" title="{channel_description}">{channel_title}</a></li>

   {if count == total_results}
      </ul>
   {/if}

{/exp:query}

Here I'm ordering them by the number of entries in the channel. If you wanted to go alphabetical by channel name, your query would end with ORDER BY channel_title ASC

If you want more fine-grained control, you could just hand-code this or use a plugin like Loopee or Low Variables or a Matrix field to create and sort a list of channels. Given that you likely won't be adding more channels very often (if ever), that's not so bad. Whatever you're comfortable with.

Linking to an archive page from entries

To list the post type for an entry and link to the archives, you'll probably do something like this:

{exp:channel:entries …}
   …

   <p>Post type: 
      <a href="{path='archives'}/{channel_short_name}">
         {channel}
      </a>
   </p>

   …
{/exp:channel:entries}

Like I did for the list of channels, notice that for the link href I'm adding the channel_short_name onto the URL.

Displaying all the posts from a particular channel

If you were using categories, the {exp:channel:entries} tag will automatically react to a category in the URL (like /archives/category/links), which is pretty handy.

But we're not relying on categories, so we have to get more clever.

Since you have the Channel Short Name as a URL segment (for example, /archives/images), you can use a segment variable to in the channel="" parameter for a {exp:channel:entries} tag:

{if segment_2}
   {exp:channel:entries 
     channel="{segment_2}"
     dynamic="no"
     limit="100"}

      {!-- CODE FOR EACH ENTRY --}

   {/exp:channel:entries}
{/if}

Segment variables are the jam.

And if you want to organize posts by more than just their channel?

If you wanted to attach more metadata to posts, that's where you could use EE's categories, or you might want to look into a tagging system, like Solspace's Tag, which is what I use.

If you're doing this for fun, it's easier and more fun to just write new tags than it is to create new categories in the CP panel.

Just sayin'.

Custom styles for different types of posts

This is easy. For each entry, add the {channel_short_name} for the entry as a class, like so: 

{exp:channel:entries …}

   <div class="post {channel_short_name}">
      …
   <div>	

{/exp:channel:entries}

The channel short name should be OK to use as a class name in your HTML.

Then, in your CSS, you've got a hook.

/* Styles for image posts */
div.images {
   …
}

/* Styles for movie posts */
div.movies {
   …
}

Cool stuff.

Create custom publish page layouts

Since I'm using only one custom field group, I have to make sure that the publish page each channel only shows the fields that matter.

In ExpressionEngine 1, you can do this using Brandon Kelly's Gypsy extension, which lets you assign any custom field to any weblog.

Gypsy hasn't been (and won't be) ported to EE2. No matter; you can make a custom publish page layout for every channel, omitting all of the fields that don't matter for each channel. Here's what I do for every channel:

  1. Create three tabs: "Publish," "Options," and "Revisions"
  2. I remove every custom field from the publish tab.
  3. On the Publish tab, I add back the custom fields that matter for the channel. For example, for the "Link" channel, I only need the field for the link's URL and a body.
  4. To the Publish tab, I also add in common or mandatory fields, like the title, entry date, and tags.
  5. On the Options tab, I place fields that I likely won't need to tinker with often, like Author or Expiration date.
  6. On the Revisions tab, I put the (duh) Revisions field.

I believe it was Lea Alcantara in one of the ExpressionEngine podcasts that suggested custom page layouts could take the place of Gypsy in EE2.

A caveat about custom page layouts

The one catch with custom publish layouts in EE2 is that if you add or remove a custom field, you'll probably have to re-do your layout for every channel. That's kind of a drag. (Literally. There's a lot of drag-and-dropping).

For that reason, I would make the custom layouts last.

Create custom bookmarklets

If you like using bookmarklets to quickly post to your site from your browser (Tumblr's bookmarklet is a killer feature), check out Wouter Vervloet's post on rapid content entry in EE2. Great find, there.

Post and be happy

Now have fun sharing!

Am I doing it wrong?

Comments? I don’t do open comments. Life is too short.

If you have something to say, get in touch via .(JavaScript must be enabled to view this email address) or on Twitter. If it's interesting, I'll post it.