10

I'd like to know if/how Stash embeds could be used in a particular way. I'm trying to keep things DRY.
Generally my templates look something like this:

_wrapper.html

<html>
<body>
    <header>...</header>
    {exp:stash:get name="content"}
    <footer>...</footer>
</body>
</html>

template.html

{!-- My 'view' --}
{exp:stash:set name="content"}
    <h1>{exp:stash:get name="title"}</h1>
    <p>{exp:stash:get name="description"}</p>
    <div class="sidebar">
        <p>Content that would be repeated across templates.</p>
    </div>
{/exp:stash:set}

{!-- My 'model' --}
{exp:channel:entries ...}
    {exp:stash:set}
        {stash:title}{title}{/stash:title}
        {stash:description}{description}{/stash:description}
    {/exp:stash:set}
{/exp:channel:entries}

The <div class="sidebar"> might have something like a list of categories.
What I thought I could do was put that div in a Stash embed and then continue writing in the view/model format. It's not turning out that way.

In template.html I would replace the <div class="sidebar"> with {stash:embed:sidebar}. Then sidebar.html would look like this:

{!-- Another 'view' --}
<div class="sidebar">
    <h2>Categories</h2>
    {exp:stash:get_list name="categories"}
        ...
    {/exp:stash:get_list}
    <h2>Something Else</h2>
    {exp:stash:get name="something_else"}
</div>

{!-- Another 'model' --}
{exp:channel:categories ...}
    {exp:stash:append_list name="categories"}
        ...
    {/exp:stash:append_list}
{/exp:channel:categories}

At the moment, {stash:embed:sidebar} does get embedded, but without the stuff inside {exp:stash:get_list} and {exp:stash:get}.
If this can be done, I'm sure a parse order issue. Perhaps a parameter on the embed tag would help. Although I don't know what.
If it can't be done, any suggestions for alternatives would be appreciated.

Thanks!

34

For me, one of the most compelling reasons to use a "template partials" approach in EE (or model-view-viewModel - MVVM - if you prefer) is being able to separate out the presentation of your data (e.g. the html markup) from the business and model logic that retrieves and formats the data (the template tags, conditionals, data formatting and so on). This decoupling of concerns has many advantages, such as the ability to easily change the presentation layer, to re-use layout templates, and to cache the captured data in a structured form and format it after retrieval from the cache.

While your question relates to a specific parse-order issue, I'm going to outline an approach, based on this idea of de-coupling the view and viewModel, which means you won't encounter parse order problems in the first place.

First, work out a common nonemaclature to describe the elements of a page into which you are going to inject content - regardless of where in your page layout(s) they might be used. To avoid collisions, don't use names that you are using for custom fields. E.g.:

{page_title}
{page_description}
{page_body}
{page_aside}
{page_nav_main}

Now work out which blocks of markup are likely to be shared across ALL layouts, and make them snippets.

{sn_head} and {sn_footer} are obvious examples, but as you put your layouts together you'll find many other blocks of markup that can and should be abstracted into snippets to keep things DRY.

{sn_head}

<head><title>{stash:page_title}</title></head>

{sn_footer}

<footer>footer</footer>

Then work out the main page layouts (the 'wrappers') for your site. For example, you might have a layout with a sidebar for internal pages, and one without for the landing page for each of your main sections. Make a /layouts/ folder in your Stash template directory and put these in:

/layouts/landing.html

<!DOCTYPE html>
<html>
    {sn_head}
    <body>   
        <h1>{stash:page_title}</h1>
        <p class="description">{stash:page_description}</p>
        <div class="bodycopy">
            {stash:page_body}
        </div>
        {sn_footer}
    </body>
</html>

/layouts/standard.html

<!DOCTYPE html>
<html>
    {sn_head}
    <body>  
        <h1>{stash:page_title}</h1>
        <p class="description">{stash:page_description}</p>
        <div class="bodycopy">
            {stash:page_body}
            <div class="sidebar">
                {stash:page_aside}
            </div>
        </div>
        {sn_footer}
    </body>
</html>   

(Note that these may not look that different, but what if that changed in the future? Make a template for each layout that has a specific purpose in your site architecture)

You want to minimize or eliminate the passing of markup from your viewModel to your view, so if you have any sub-templates that provide formatting for a particular region of a layout, create a folder for them called 'partials'. E.g., 'sidebar_list' would make a good partial:

/partials/sidebar_list.html

<h2>{stash:aside_title}</h2>
<ul>
{exp:stash:get_list name="aside_list"}
    <li>{item_name}</li>
{/exp:stash:get_list}
</ul>

To make use of the layout templates your EE template (the 'ViewModel') needs to grab some data, include the relevant layout and inject sub-templates where needed:

viewModel

{!-- set the page layout --}
{stash:embed:layouts:standard} 

{!-- inject the sidebar_list partial into the {stash:aside} region --}
{exp:stash:set_value name="page_aside" value="{exp:stash:embed:partials:sidebar_list}"}

{!-- main page variables --}
{exp:channel:entries limit="1" channel="blog" disable="member_data|pagination" require_entry="yes"} 
    {sn_set_entry_fields}
{/exp:channel:entries}

{!-- sidebar variables  --}
{exp:stash:set name="aside_title"}Categories{/exp:stash:set}
{exp:stash:set_list name="aside_list" parse_tags="yes"}
    {exp:channel:categories}
        {stash:item_name}{category_name}{/stash:item_name}
    {/exp:channel:categories}
{/exp:stash:set_list}

If you have more than one viewModel, you're likely to be repeating the same set code. So encapsulate that as a snippet too:

{sn_set_entry_fields}

{exp:stash:set}
    {stash:page_title}{title}{/stash:page_title}
    {stash:page_description}{cf_description}{/stash:page_description}
    {stash:page_body}{cf_body}{/stash:page_body}
{/exp:stash:set}
| improve this answer | |
  • Very in depth. I think what you mentioned about having multiple layout files is the solution I'm probably looking for. Usually I had one wrapper for every page but it seems that isn't always the right way to do it. Thanks. – Jason Varga Dec 17 '12 at 23:46
  • I'm just getting my head around the the DRY template partials approach so this was an excellent read, thanks Mark. One question though - I noticed you're abstracting a lot of markup into snippets. Are you worried that the overhead of using so many snippets (from a query/execution time/memory usage point of view) is outweighing the benefit of being DRY and avoiding template embeds? Or are snippets so lightweight that it's not a concern? – Stephen Jan 14 '13 at 18:39
  • 5
    Snippets are retrieved with a single query (good) for every template load regardless of whether the template makes use of them (bad). So they are best used for chunks of markup or tags that are used throughout the site, not just in a few places. The template parser also iterates over the array of snippets (globals) multiple times, replacing placeholders in the templates it parses; it can help therefore to keep your snippets small. If you keep those principles in mind snippets are the best way to encapsulate small reusable chunks of template code. For everything else I recommend Stash embeds. – Mark Croxton Jan 16 '13 at 10:04
  • Hi Mark, this example worked perfectly for me apart from the code in the snippet. I had to add the 'exp' part i.e. {exp:stash:page_title}{title}{/stash:page_title} I'm using 2.3.4 and not putting these in a snippet (yet) so that may be the key :) – Rob Hodges Mar 12 '13 at 13:14
  • 1
    Not really a bug, but a rather subtle detail of Stash's design. Stash variables with null values are not removed at the end of a parsing pass because they may be needed as placeholders in further recursive passes of the template parser that exposes more variable set code. If you need variables with empty values to be removed from the final output automatically then you can use 'snippet' as the variable type type="snippet" when setting, and then use as a standard EE snippet {my_var}. – Mark Croxton Mar 18 '13 at 19:47
13

Here is the basic concept. I personally don't use the list tags to set a ton of individual variables. I find that it's too much and too tedious, and can perform an excess amount of queries if you have a lot of variables. I guess though it really goes down to how you prefer to organize your templates.

{exp:channel:entries}

    {stash:embed:your_context:your_template process="start"}

{/exp:channel:entries}

The above example would be embed into the channel entries BEFORE the entries loop is parsed. This for when you are constantly reusing the same markup in the loop, but the channel entries tag is different.

And then if want to embed a template after something has parsed, do this:

{stash:embed:sidebar process="end"}

It's really just manipulating the parsing order to get your tags to fire in the order you need.

Take a look at the homepage code from my site, objectivehtml.com. This is index.html of the default template group.

{exp:stash:set}

    {stash:page_title}Objective HTML - Top Notch ExpressionEngine Add-ons{/stash:page_title}
    {stash:page_class}home{/stash:page_class}
    {stash:section_title}Featured Product{/stash:section_title}

    {stash:page_sidebar}{stash:embed:objects:main_sidebar}{/stash:page_sidebar}

    {stash:page_content}

        {exp:channel:entries
            channel="articles"
            dynamic="no"
            disable="member_data|categories|category_fields|pagination"
            limit="1"
            status="featured"
        }

            {stash:embed:objects:article process="start"}

        {/exp:channel:entries}

    {/stash:page_content}

{/exp:stash:set}

{stash:embed:structure:header}

This is article.html, which is what I call a template object. I can insert an article on any page, just by including the article "object". And since I parse it first, it's included before any of the other tags have had a chance to parse.

<article class="article">

    {if segment_3 != url_title}

        <h3><a href="/articles/read/{url_title}">{title}</a></h3>

    {if:else}

        <h1>{title}</h1>

    {/if}

    {if !segment_3 && segment_2}

        {if article_excerpt}
            <p>{article_excerpt}</p>

        <p><a href="/articles/read/{url_title}" class="button small">Learn More <span class=    "icon-arrow-right"></span></a></p>

        {if:else}
            {article_content}
        {/if}

    {if:else}
        {article_content}
    {/if}

</article>
| improve this answer | |
  • That makes sense but I tried to show in my example that the embed I'm using isn't wrapped by a channel entries tag. It may contain (multiple) channel entries tags within it, though. I pretty much just want to have a reusable chunk of code. – Jason Varga Dec 16 '12 at 0:35
  • That example applies to any tag. Embed any chunk of reusable content which is parsed before the wrapping tag. – Justin Kimbrell Dec 16 '12 at 3:59
  • Using any of the 3 process parameters still leaves me with the same result though. – Jason Varga Dec 16 '12 at 4:43
  • 1
    I update my code with a better example. It definitely work, I grabbed the code from my own site. – Justin Kimbrell Dec 16 '12 at 13:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.