Blog

Snippets and Preload Replace Variables

17 November 2011 36 comments

At the excellent EECI conference in New York last October, I talked about ExpressionEngine’s Parse Order. In the slides, I hid a little trick I recently rediscovered: putting preload replace variables in snippets. This combination will make your snippets even more useful than they already are. Here’s why.

Yo Dawg!

According to EE’s parse order, snippets are parsed first, before preload replace variables. These, in turn, are parsed before plugin and module tags, so both can be used as early parsed variables. This combination makes it possible to change the content of your snippet on a per template basis. Much like embeds and embed variables, but without the performance hit. Still confused? Take a look at this snippet we’ll conveniently call {sn_channel_entries}:

{exp:channel:entries channel="{pre_channel}" limit="{pre_limit}"}
  <h2>{title}</h2>
  <div class="intro">
    {cf_{pre_prefix}_intro}
  </div>
  <p class="more">
    <a href="{url_title_path="{segment_1}/{pre_more_template}"}">
      Read more
    </a>
  </p>
  {if no_results}
    <p>{pre_no_results}</p>
  {/if}
{/exp:channel:entries}

Let’s assume we’ve got 2 channels: Stories and Reviews. They’ve got different field groups assigned to them, one containing the field {cf_story_intro} and one with the field {cf_review_intro}. On the page example.com/stories we want to show a list of our Stories, and on the page example.com/reviews, well, take a wild guess. These pages will look roughly the same, so we want to re-use as much code as we can, which is why we created the snippet. Now, we can create this stories/index template:

{embed="home/_header" title="{pre_title}"}

{preload_replace:pre_title="Stories"}
{preload_replace:pre_channel="stories"}
{preload_replace:pre_limit="10"}
{preload_replace:pre_prefix="story"}
{preload_replace:pre_more_template="read"}
{preload_replace:pre_no_results="No stories found."}

<h1>{pre_title}</h1>
{sn_channel_entries}

{gv_footer}

Parse Order dictates that this will end up as the following template, just before the tags are parsed, assuming that {gv_footer} is a late parsed global variable:

{embed="home/_header" title="Stories"}

<h1>Stories</h1>
{exp:channel:entries channel="stories" limit="10"}
  <h2>{title}</h2>
  <div class="intro">
    {cf_story_intro}
  </div>
  <p class="more">
    <a href="{url_title_path="stories/read"}">
      Read more
    </a>
  </p>
  {if no_results}
    <p>No stories found.</p>
  {/if}
{/exp:channel:entries}

{gv_footer}

Excellent! That’s exactly what we need to show a list of stories. Now then, how about the Reviews? Just imagine what the template will look like before tags are parsed with this code to start with, in the template reviews/index:

{embed="home/_header" title="{pre_title}"}

{preload_replace:pre_title="Reviews"}
{preload_replace:pre_channel="reviews"}
{preload_replace:pre_limit="10"}
{preload_replace:pre_prefix="review"}
{preload_replace:pre_more_template="details"}
{preload_replace:pre_no_results="No reviews found."}

<h1>{pre_title}</h1>
{sn_channel_entries}

{gv_footer}

That’s right, a list of reviews. And notice that the only thing changed here is the value of the preload replace variables. But the fun doesn’t stop there.

But wait, there’s more

Here’s something you might not know about preload replace variables: if you add multiple values of the same preload replace variable in one template, the first one is evaluated, the rest are ignored. How is this important? Well, this means you can use simple conditionals to change the value of the preload replace variable to suit your needs better. Say you want to display the same list of Stories by category, but you want a different title or limit when showing categories? Well then, if you’re using Low Seg2Cat and your url looks like example.com/stories/category/fantasy, then here you go:

{embed="home/_header" title="{pre_title}"}

{if segment_2 == 'category'}
  {preload_replace:pre_title="{segment_3_category_name} Stories"}
  {preload_replace:pre_limit="25"}
  {preload_replace:pre_no_results="No stories found in this category."}
{/if}

{preload_replace:pre_title="Stories"}
{preload_replace:pre_channel="stories"}
{preload_replace:pre_limit="10"}
{preload_replace:pre_prefix="story"}
{preload_replace:pre_more_template="read"}
{preload_replace:pre_no_results="No stories found."}

<h1>{pre_title}</h1>
{sn_channel_entries}

{gv_footer}

The simple conditional will be evaluated before the preload replace variables, so their values will be different when viewing a category page. Also, they don’t need to be on the top of your template to work; anywhere is fine. As you can see, the flexibility of the snippet just increased by quite a bit, whilst retaining its efficiency in terms of performance. Another nail in the coffin of embeds.

Comments

  1. Ian Pitts 17 November 2011 at 15:48

    Holy crap, Low! This is amazing!

    Mind = Blown.

  2. Brian 17 November 2011 at 16:02

    Basically the next time you start writing {embed=”, stop right there and try it with a snippet.

  3. Erwin Heiser 17 November 2011 at 16:06

    Impressive. Most impressive.

  4. Garrett Winder 17 November 2011 at 16:17

    I always knew you were good for something.

  5. moonbeetle 17 November 2011 at 16:33

    Nice! In fact I was doing something similar but on template level.
    I know a lot of people use a singular/plural when naming custom fields (except when they have a channel called “news” ;-) I always use plural for both the channels and the custom fields.  Resulting in placeholders like:  {cf_{pre_channel}_intro}

  6. Tyler Herman 17 November 2011 at 16:42

    Thanks for sharing. I think a lot of people are going to be changing the way they put templates together because of this.

  7. Mike Mella 17 November 2011 at 17:22

    This is awesome, but I’m a little confused about the parsing…

    If Snippets are parsed before Preload Replace variables, then how does the channel entries tag in the Snippet recognize the values of the Preload Replace variables?

  8. Lodewijk Schutte 17 November 2011 at 17:27

    @Mike Mella: “Parsed” in this context means “replaced”. So, first the snippet marker, {sn_channel_entries}, is replaced with the content of the snippet. Then the preload replace vars are replaced, and finally the tags are processed. The snippet isn’t parsed before it hits the template, but rather its content is placed in the template, which parses the code.

  9. Mike Mella 17 November 2011 at 18:22

    Ah.  Got it now. 

    Thanks!

  10. Bart Houben 17 November 2011 at 21:20

    Thanks for this great tip Low. This is an insight that will solve a lot of performance issues!

  11. Jannis Gundermann 17 November 2011 at 21:34

    This is great! I’ve finally wrapped my head around this now thanks to this article.

    One thing I wonder though is with Snippets being stored in the database how does this affect your version control workflow and the ability to push updates to a site across multiple dev/server environments?
    Do you always need to sync the databases for each change to a snippet, or do you change locally then copy paste said snippet into all the other sites manually? Both seem options seem less than ideal.

    I guess the one nice thing about the embeds is that its based in the file system so I can just tell my GIT to update each server et voila everything is in sync and running within seconds…

    Would love to hear your thoughts on this.

    Thanks.

  12. Lodewijk Schutte 17 November 2011 at 23:21

    @Jannis Gundermann: there are several 3rd party add-ons that allow you to store snippets as files, like LibrarEE and Snippet Sync. Heck, I might even add the feature to Low Variables myself. And, in the end, I’ll take site performance over ease of development any time.

  13. Richard 18 November 2011 at 04:34

    @Jannis:

    Use Mount:ee.
    Wonderful help and will solve any workflow issues using snippets.

  14. Jannis Gundermann 20 November 2011 at 23:46

    Thanks guys for the suggestions!
    Both addons look great, and if I might sneak a litte +1 vote for this being a LV feature in here too that would be nice.

    Oh and yes, at the end of the day performance is of course the most important.

  15. Natetronn 24 November 2011 at 20:38

    It’s funny, when I started learning EE I didn’t know you could use embed variables (or that they even existed!) At that time I found and used Snippets and Preload as described in your article. Glad I did because apparently it’s the better way of doing things.

    I have only ran into a few things like Matrix and Reverse Related Entries needing to be in embeds though, those are super rare cases: http://help.pixelandtonic.com/brandonkelly/topics/php_errors_on_reverse_related_entries

    Now, besides super rare cases, is there ever a reason to use embeds?

  16. Lodewijk Schutte 25 November 2011 at 09:14

    @Natetronn: only when you run into conflicting nested tags — like your Matrix example — or if you need to use both PHP on input and output.

  17. George Merlocco 12 January 2012 at 20:18

    Thanks very much for this Lodewijk! As an EE noob, I want to be sure that I develop good habits for efficiency with my EE templates. I will be studying this one and doing my best to implement it. Cheers!

  18. Russ Back 10 February 2012 at 11:32

    This is fantastic. Can you think of a way to have advanced conditionals working? For example I need to set vars on an if/ifelse/else basis. I assume this isn’t possible as you say that only the first conditional is evaluated?

  19. Jan Van Lysebettens 16 March 2012 at 09:20

    You, sir, are a genius.

  20. outline4 29 March 2012 at 20:29

    I thought I was the first to discover this… published it in the EE forums, then they pointed me to your blog entry :D

    there’s even more to this tequnique:
    you can have embeds in your snippets that contains snippets as well. I actually use this quite a lot now…

    there’s just one thing that you cannot do with this, maybe you can explain why:
    with this technique you cannot use a snippet within a snippet… I get memory warnings…

  21. Lodewijk Schutte 30 March 2012 at 10:07

    Using snippets inside snippets is tricky business, because the order in which they are parsed is not really set. I think the ones created first are parsed first, but that’s not explicitly set in the query when they are fetched from the database. That’s why it can lead to unexpected results.

    With Low Variables however, the early parsed variables (practically identical to Snippets) are parsed in the same order as they appear in their group, which makes nesting early parsed variables more robust.

  22. outline4 30 March 2012 at 11:09

    that’s great!
    thanks for the tip…

    p.s. did you know that this works as well (simple conditionals within preload_replace):
    {preload_replace:pre_status=”{if logged_in}open|Draft{/if}{if logged_out}open{/if}”}

    hm… I wonder if your example would work like this?
    {preload_replace:pre_title=”{if segment_2 == ‘category’}{segment_3_category_name} {/if}Stories”}

    btw: do you know a technique to change the status of a channel without using an embed?
    I typically have to embed a channel to make the {pre_status} from the example above work…
    {embed=“templates/_content” emb_status=”{pre_status}”}
    where I would like to be able to to the following:
    {channel:entries channel=“x” status=”{pre_status}”}…

    cheers
    stefan

  23. Lodewijk Schutte 30 March 2012 at 11:24

    Actually, the logged_in/logged_out conditionals aren’t simple conditionals, so that pre_status variable won’t work. Take a look at my presentation slides for Parse Order Pro, which might clarify stuff for you.

    As for your last question, if you can’t do it with simple conditionals, you’ll have to use PHP in your template, is my guess. But you’re better off asking the ExpressionEngine support crew for help on this. :)

  24. outline4 30 March 2012 at 11:59

    well - the logged in logged out stuff works… surprisingly…
    I use it on a daily basis…

    and yeah: I posted this question in the EE forums, but never got a satisfying answer. so I thought I drop it here…
    I miss the EE1 forums, when everybody hung out there :D

  25. Mark Simchock 12 April 2012 at 19:22

    I just discovered preload_replace in the EE docs and from that page this article. My first impression is that this is amazing. Once I get my head wrapped around it it’s a massive game changer, yes?

    And I thought plain ol’ Seg2Cat was good. With this technique dev time just shrunk by a noticeable magnitude.

    One thing to add: Based on what I can glean thus far, it’s possible to write the whole EE select in one shot (e.g., preload_replace:my_parms=“url=’‘...limit=’‘...categories=’‘...”) and then just preload_replace that whole instead of parm by parm. I think I prefer the whole line since each query might be slightly different. That is, use a different set of parms at different times. As a one liner I wouldn’t have to worry about it.

    Which this no pops into my head…can you chain preload_replaces? Not that I can think of reason why I would want to, none the less if it’s possible perhaps there’s a use for such a trick?

    Thanks Low.

  26. Lodewijk Schutte 13 April 2012 at 10:13

    Hi Mark. Adding a whole EE select is definitely possible. As for chaining preload_replaces, I’m not quite sure what you mean by that.

  27. Mark Simchock 13 April 2012 at 16:47

    Re: Chaining

    Presuming the parsing order also applies to preloud_replace then could one preload_replace “variable” be a value in another preload-replace “variable” futher down the page.

    While none some to mind at the moment, I would think there might be instances where such built-it-on-the-fly-ability would be useful.

  28. Mark Simchock 13 April 2012 at 17:08

    btw, best I can tell it doesn’t like conditionals with else. I had to take an if >  elseif >  else and convert it to 3 different ifs.

    Can anyone else confirm? Or am I assuming something that is ultimately incorrect?

  29. Lodewijk Schutte 16 April 2012 at 10:44

    Chaining in that sense should be possible, but use it with caution, would be my advice.

    And yes, setting preload_replace vars with if-statements only works with simple conditionals. Advanced conditionals are parsed too late. Check the Parse Order Pro slides and the Parse Order pdf for reference.

  30. Mark Simchock 17 April 2012 at 17:07

    Low - Someday when they open the ExpressionEngine Hall of Fame I will personally nominate you. No doubt you are first ballot material. A true asset to the EE community.

  31. Jannis Gundermann 29 April 2012 at 08:56

    I’ve been trying to figure out a way to use these lovely snippets with more dynamic data recently where the custom field name I’d be calling needs to be set based on the channel_short_name of an entries loop.

    So far I’ve been unsuccessful but before I give up I thought I’d better ask for some ideas around this topic here.

    The idea being that within my channel:entries loop I can use {cf_{channel_short_name}_myfield} and have it working.

    Any thoughts as to whether or not this is possible?

  32. Mark Simchock 30 July 2012 at 01:53

    @Jannis Gundermann - I’m still experimenting with this as well. That said, I think (if it’s not working then) {channel_short_name} comes too late in the parse order. You can however, check the segment and then do preload replaces based on that.

    The major limitation seems to be the fact that you can only sure simple conditionals. and then they say simple, they mean pretty simple. I’m working with slightly more complex channel and segment structure and I think I’ve hit a dead end. Which leads me to…

  33. Mark Simchock 30 July 2012 at 01:57

    Re: simple conditionals

    Low - since PHP on Input is parsed a couple step before preload_replace, might it be possible to do something a bit more complex in terms of conditionals.

    I’m using (and love) Seg2Cat and need to test for group && parent and that doesn’t seem to be simple enough.

    Your thoughts?

  34. Lodewijk Schutte 30 July 2012 at 10:00

    Using something like {cf_{channel_short_name}_myfield} is tricky, since both variables are parsed during stage 5 (according to my parse order pdf), and there’s no way of knowing for sure which comes first within that stage. You’re better off using either a segment variable or another early parsed var.

    Mark, you can nest simple conditionals. As long as you make sure there is no {if:else} somewhere inside it (even if it belongs to another conditional). You should be able to use {if segment_n_category_group_id == ‘1’} {of segment_n_category_parent_id == ‘0’} ... {/if} {/if}

    Using PHP is an option, too. As long as you know how to get to the variables, of course.

  35. Mark Simchock 30 July 2012 at 14:20

    Thanks Low.

    Pardon me (it’s my first coffee) but “simple conditional” also means no && or || correct? That’s where I seem to be running into problems. No errors - usually - but the first conditional was being ignored and EE was doing the first group of preload_replace vars and not necessarily the ones that it should. That is, it was as if I didn’t even have a conditional.

    While probably minor in general, on this project the lack of if:else is somewhat of a concern in terms of unnecessary processing overhead. If…if…if… = 2 ifs too many ;) Between the wonky && / || issue and the lack of if:else, I’ve decided to move my coding party to embedded PHP. I’ll echo out the lines of code and all should be well. I hope :) 

    btw, I was also getting some EE parsing errors on ifs that used Seg2Cat variables. I ended up sidestepping that by breaking out pieces into other embeds. I didn’t seem to have a choice. That said, I like to try to keep my embeds to a minimum to reduce overhead. I also couldn’t use embeds with preload_replaces to this is why I’m kinda forced to resort to PHP.

    All that said, I *love* the preload_replace trick. If it means some PHP so be it. Now that I’ve sorted the no if:else problem the trick gets even stronger, eh? :)

  36. Hung Le 23 October 2012 at 20:51

    This is great but it would be much nicer if you can make Low Title have the same parse order as Low Seg2Cat variable. So then you can get rid of those embeds template entirely?

    Well yes, this’s a feature request ;)

     

Commenting is not available in this channel entry.