Learn

Share Your Knowledge!

This fast-growing section of our site is the new one-stop shop for educational materials for ExpressionEngine with rich and expert content, from both EllisLab and the community. Submit your article, tip, or solution today!

  • Downloadable iCalendar Files

    in: Tips, Tutorials, Templating

    This tutorial will show you how to create downloadable iCalendar files. With ExpressionEngine, it’s easy-peasy because you can use your well-structured content however you need. You can use the same entries you use for your web site to create shareable .ics iCalendar format files. Here’s how in three easy steps.

    1. Install the “Download Content” plugin.
    2. Create your iCalendar format template.
    3. Build a link to your iCalendar template.

    Install the free Download Content plugin

    In order to make your ExpressionEngine-generated .ics files be treated like a downloadable file, grab and install the free Download Content plugin.

    • Download the plugin from GitHub.
    • Unzip and upload the enclosed download_content folder to /system/user/addons.
    • Visit the Add-on Manager in your control panel, and click Install for the “Download Content” plugin.

    Create your download template

    Now we need to create an endpoint that we will link to that allows our visitors to download our .ics file. In this example, I’m creating an iCalendar file for a free concert the local brewpub is putting on. I have created a template named add-to-my-calendar inside my concerts template group.

    It uses the entry date and expiration date of the concert, the title, and a custom field called meta_description. Side note: meta_description is part of my standard set of SEO fields that I add to every channel. Repurposing it for the description of the calendar event makes sense. I’m also going to take advantage of a few iCalendar fields to setup a default alarm two hours before the event.

    {exp:channel:entries channel='concerts' limit='1' show_future_entries='yes' require_entry='yes'}
    	{if no_results}
    		{redirect='404'}
    	{/if}
    
    {exp:download_content filename='{url_title}.ics'}
    BEGIN:VCALENDAR
    VERSION:2.0
    BEGIN:VEVENT
    UID:downloadable-icalendar-files-{entry_date format='%Y%m%d'}@{site_name:url_slug}
    DTSTART;TZID={entry_date format='%e:%Y%m%dT%H%i%s'}
    DTEND;TZID={expiration_date format='%e:%Y%m%dT%H%i%s'}
    SUMMARY:{title}
    X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC
    LOCATION:The BrewPub\n62950 NE 18th St\nBend\, OR 97701
    BEGIN:VALARM
    X-WR-ALARMUID:downloadable-icalendar-files-{entry_date format='%Y%m%d'}@{site_name:url_slug}
    TRIGGER:-PT120M
    DESCRIPTION:{meta_description}
    ACTION:DISPLAY
    END:VALARM
    END:VEVENT
    END:VCALENDAR
    {/exp:download_content}
    
    {/exp:channel:entries}

    Let’s break down the tags:

    {exp:channel:entries channel='concerts' show_future_entries='yes' limit='1' require_entry='yes'}
    
    • show_future_entries= lets us use the Entry Date as the start time of the event. Since we want people to see events that haven’t happened yet, we tell the Channel Entries tag to show future events.
    • limit='1' require_entry='yes' is a common design pattern for single-entry templates. This makes sure we only show one entry, and we don’t want it to display anything if the URL title is invalid. We combine it with:
    {if no_results}
        {redirect='404'}
    {/if}
    

    If there are no results—an invalid URL, an entry that’s already expired, a closed status, etc.—we will have proper 404 behavior.

    The next tag is the Download Content plugin we installed earlier.

    {exp:download_content filename='{url_title}.ics'}
    

    Using the entry’s URL title for the filename isn’t a requirement, but it will help the visitor identify the file’s purpose after downloading. It needs an .ics extension so desktop and mobile calendar apps know that the file is for them. This plugin makes sure the output only contains what’s inside the plugin’s tag, and gets treated as a download instead of displaying it on the screen.

    iCalendar files are picky about whitespace. This is why the code is not indented like we would normally encourage.

    The rest of the contents are made from standard iCalendar file format attributes. You can read more about that file format and its attributes here: iCalendar file format. Here are some notes to help dissect our template decisions:

    • UID: Providing a UID allows the calendar app to change the existing event if it is updated. Otherwise adding a new version will create a new event. The URL title, date, and site name should give us a unique ID. The :url_slug variable modifier replaces spaces with slashes and so forth so we can use the site’s name here.
    • DTSTART/DTEND: The format parameter used here (%e:%Y%m%dT%H%i%s) will output: America/Los_Angeles:20180827T105300
    • We use the entry_date for the start date and the expiration_date for the end date.
    • SUMMARY uses the entry’s title for the event’s name
    • Some calendar apps may not use the DESCRIPTION, but we add it for those that do. Likewise, we add DTSTAMP because Outlook demands it.
    • TRIGGER:-PT120M sets an alarm for 120 minutes (2 hours) before the event
    • X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC sets an alarm for Apple clients when the person needs to leave their current location to make it to the event on time.

    Create a download link

    Now all we need to do is link to our .ics file and we’re good to go! From my concert listing page, I’ll add:

    <a href="{url_title_path='concerts/add-to-my-calendar'}">Add this concert to my calendar!</a>
    

    Now when I click the link, it downloads a properly formatted .ics file that I can add to my calendar!

    With this knowledge you can create events, todos, journal entries, and any other kind of iCalendar file you can imagine. Use these same principles to output your content in other file formats, too; the sky’s the limit! With ExpressionEngine, you never have to work hard to make your content work hard for you.

    | Read in 5 minutes
  • Getting Started: A 10-Minute Primer

    in: Tutorials, Templating

    Get started using ExpressionEngine FAST. This primer will introduce you to the basic concepts needed to master ExpressionEngine, and build a fully functional blog in just 10 minutes!

    Learn the basics of building a beautiful web site with ExpressionEngine and make a fully functional blog from scratch in just 10 minutes!

    Subscribe to future videos here: http://bit.ly/ExpressionEngineTV

    Music: https://www.bensound.com

    Transcript

    ExpressionEngine is one of the web’s most flexible content management systems.You can create, really, any kind of website with it. But we wanted to make a quick primer for those new to ExpressionEngine to show how all the pieces work together; how you put in content, how you display that content.

    And so, we’re just going to make a simple blog for now, it’s a type of site that we’re all familiar with so it should make things easy to understand. So, I’ve got a fresh install ready here, a blank canvas to create our site, and I’ve logged into the control panel.

    The first thing I like to do is name my site, and there’s this button up here that’s kind of calling out to me to do that, so we’ll click through and we’ll just call it, “My Blog.” And we’ll update the short name as well, and we’ll click Save. And you can see the new name shows up right here in the top left of our control panel.

    Now from here, we need to set up a way to enter our blog’s content, and to do that in ExpressionEngine, we need to setup a Channel. Now, Channels are basically the different types of content your site has, and your content lives in these Channels. Maybe think of them as the buckets for your different types of content.

    So, if your site has events, you might have an events Channel, or if your site has recipes, you might have a recipes Channel. But the important thing with Channels is that allow you to break free of the idea of content living on a certain page on your site, we don’t really have to think about that now, we just want our content to be able to stand on its own, and then ExpressionEngine will allow us to display it on any part of the site that we want and within any design that we want.

    So for our blog, we just need to make one simple Channel. And to do that, we’ll go the Developer menu and click Channels, and then Add New. And we’ll just call it, “Blog.”

    Next we want to add the fields, or the different parts of our content that we want to capture separately. So again, if you had an events Channel, you might use some Date fields to capture some dates, or your recipes Channel might use a Grid field to capture all the steps and ingredients that appear in a recipe.

    But for our blog, we at least want to capture the blog entry text itself, and for that, I just want to use a simple Textarea, we’ll call it, “Body.” And I want to choose Markdown as the formatting type because I want to keep my content as clean and as portable as I can, and you can install this formatting type via the Add-ons page if the option isn’t showing up here for you. And with that, we’ll click Save, and you can see it shows up here.

    But there’s a couple more fields I think I might want, one is an excerpt field, in case I want more control over the excerpt that appears on my blog homepage. So we’ll create that the same, maybe make it a little smaller.

    And I also think I want a featured image be a part of my blog posts. and for that I want to use a File field so I can upload a file directly from the publish form. We’ll call it, “Featured Image.” And to use the file field you do need to have set up an Upload Destination in ExpressionEngine which I must confess I did set up prior to this so I didn’t have to break the flow, but it’s a simple thing to do. And with that, we’ll save our Channel.

    And you’ll see it now appears under the Create menu, and if we click through, you’ll see all the fields that we just created. So this is ready for me to post my first blog entry. I’ll just paste in some placeholder content here.

    So we have our first entry, but how do we display this on our website? For that, we need to write some templates. So we’ll go to the Template Manager, and first need to make a Template Group for our templates to live in. So we’ll just call it blog, and we’ll keep it the default group, and we’ll click Save. And when we make a Template Group, it automatically creates an index template for us, and since this Template Group is our default Template Group, this index template is actually our site’s homepage.

    Now ExpressionEngine lets us edit the template within the control panel here, but when you create a template, it also creates a file that you can edit with a text editor, and many folks prefer to just work with that file directly. So I’m going to do that. So here’s our installation, I’m going to go to system, user, templates and here is that same site short name that we set up earlier, so our templates must be in here. And here is that same index template that we just created under our blog Template Group.

    Now one of the things that makes ExpressionEngine stand out is its easy, clean, but powerful template language. If you know HTML, it should look pretty familiar and be easy to understand. For example, say you wanted to display the site name that we set up earlier. Instead of angle brackets like in HTML, ExpressionEngine’s template tags and variables use curly braces. And of course you can write any HTML you like around these variables to make them look how you want.

    So we’ll save this, and now let’s take a look at our homepage. And there is the site name that we set up earlier surrounded by those H1 tags. But let’s start writing the code that shows us our blog entries. I’m going to start out with an opening curly brace, and type exp:channel, because our blog content is in a channel, and we want the entries from that channel.

    Now here’s where the HTML knowledge is going to come in handy because we’re going to add some attributes, or parameters, to this tag. First we need to tell it which channel we want to pull from because we can have all kinds of channels, we want to pull from the “blog” channel. And since this is our homepage, we want to show the newest entries first so we want to order by the entry date and we want to sort descending. And let’s say we only want to show the most recent 10 entries. And again like in HTML, we’re going to write a closing tag.

    Now in between these two tags is where we’re going specify how we want our content displayed. And whatever is inside the tags here is going to repeat for each blog entry. So let’s say we want to show the entry title inside some H2 tags here. And then right below that, we want to show our excerpt. So we’ll save that and let’s go see how it looks.

    And there’s our blog entries. Here’s the one I added earlier, and then a couple more that I went back and added just to fill out the listing some more. But, it looks like this one is missing an excerpt. Maybe I forgot or just didn’t feel the need to make a special excerpt for that entry. But I still want to show a little preview of the entry so I’m going to go back to my template and add a conditional.

    And if you’re at all familiar with programming, this should make some sense. We’re basically going to say {if excerpt}, basically if the excerpt exists, show the excerpt, otherwise, take the body of the blog article and limit it to, let’s say, 200 characters. And we’ll close the conditional. And we’ll refresh.

    Ah, that’s looks better. Now we’re missing one crucial thing and that’s to be able to read the full article. So, to do this, we need to make a new template. And I actually don’t need to go to the control panel to make this template, I can just make a new file inside our Template Group. So we’ll just call it, “article.” And in that template, we want to put in a Channel Entries tag like on our homepage except this Channel Entries tag is going to let the URL tell it which entry to show, and we want to make sure it shows nothing else, so we want to put this require_entry= parameter on there.

    And we’ll change this to show the article body. And next, we’ll go back to our homepage template, and add a link to view the full article. We’ll just make a link out of the title here, and we can use this handy url_title_path variable, and we’ll point it to our article template that we just set up in our blog Template Group. And what this variable will do is automatically generate a URL based on the entry’s title.

    Ok, so let’s see if this works. We have links, and we’ll click through, and hey, there’s our full article. And if we take a look at the URL up here, you can see it’s loading the article template, and it’s put a nice, readable title for this entry so that the article template knows which entry to show. And with that, our blog is pretty much finished.

    But there’s one glaring thing we haven’t addressed and it’s that this doesn’t look very good, we’re just using the default browser styles here. And that’s because, if we look at the source, ExpressionEngine doesn’t inject its own markup or CSS, you are 100% in control of that. So to fix this, I’ve found this free template that I kind of like from a template site, and it’s just the HTML, it wasn’t even built with ExpressionEngine in mind, but that’s ok, I can just take these templates, the homepage and article template here, and paste them into the templates we thet we just made, and then we just replace the static, hard-coded content with our dynamic ExpressionEngine tags. So, let’s do that.

    And that should do it. You might notice there’s a lot of duplicate code across the templates and you’ll typically want put that stuff into a Template Layout, but that’s for another video. So, let’s take a look.

    And there you go, there’s my blog entries, and you can even see I got to use the featured images that I added to the entries, I just took the custom field variable and put it in the image tag source, and it just worked!

    So hopefully by now you get the gist of how ExpressionEngine works, and you have a good jumping off point for being able to publish your universe. Now there’s a lot more to cover from Template Layouts as I mentioned, to Fluid content, relationships, custom add-on development, so much more. It’s all in our documentation, but also subscribe to this channel and we’ll try to break down these things together.

    | Read in 11 minutes
  • Smarter Breadcrumbs with Layout Lists

    in: Front-end Development, Tips, Templating

    Layout List variables make it easy to reuse content in a variety of ways.

    Define the breadcrumb name, URL and position on your content templates:

    {layout:set:append name='breadcrumb_urls'}{path='second'}{/layout:set:append}
    {layout:set:append name='breadcrumb_titles'}Second crumb{/layout:set:append}
    {layout:set:append name='breadcrumb_jsonld_positions'}2{/layout:set:append}
    
    {layout:set:append name='breadcrumb_urls'}{path='active'}{/layout:set:append}
    {layout:set:append name='breadcrumb_titles'}Active crumb{/layout:set:append}
    {layout:set:append name='breadcrumb_jsonld_positions'}3{/layout:set:append}

    Output it in whatever format you need on your layouts.

    Bootstrap’s Modern Business template’s HTML breadcrumbs:

    {layout:breadcrumb_urls}
        {if count == 1}
            <ol class="breadcrumb">
                <li class="breadcrumb-item">
                    <a href="index">Home</a>
                </li>
                <li class="breadcrumb-item">
                    <a href="{value}">{layout:breadcrumb_titles index='{index}'}</a>
                </li>
        {if:elseif count < total_results}
                <li class="breadcrumb-item">
                  <a href="{value}">{layout:breadcrumb_titles index='{index}'}</a>
                </li>
        {if:else}
                <li class="breadcrumb-item active">
                    {layout:breadcrumb_titles index='{index}'}
                </li>
            </ol>
        {/if}
    {/layout:breadcrumb_urls}

    A structured markup BreadcrumbList:

    <script type="application/ld+json">
        {
            "@context": "http://schema.org",
            "@type": "BreadcrumbList",
            "itemListElement": [
            {
                "@type": "ListItem",
                "position": 1,
                "item": {
                    "@id": "https://example.com/index",
                    "name": "Home"
                }
            }
            {layout:breadcrumb_urls}
            ,{
                "@type": "ListItem",
                "position": {layout:breadcrumb_jsonld_positions index='{index}'},
                "item": {
                    "@id": "{value}",
                    "name": "{layout:breadcrumb_titles index='{index}'}"
                }
            }
            {/layout:breadcrumb_urls}
        ]}
    </script>

    DRY, flexible, easy to maintain and update. Your final result will look like:

    <ol class="breadcrumb">
        <li class="breadcrumb-item">
            <a href="index">Home</a>
        </li>
        <li class="breadcrumb-item">
            <a href="second">Second crumb</a>
        </li>
        <li class="breadcrumb-item active">
            Active crumb
        </li>
    </ol>
    
    <script type="application/ld+json">
        {
            "@context": "http://schema.org",
            "@type": "BreadcrumbList",
            "itemListElement": [
            {
                "@type": "ListItem",
                "position": 1,
                "item": {
                    "@id": "https://example.com/index",
                    "name": "Home"
                }
            },
            {
                "@type": "ListItem",
                "position": 2,
                "item": {
                    "@id": "https://example.com/second",
                    "name": "Second crumb"
                }
            },
            {
                "@type": "ListItem",
                "position": 3,
                "item": {
                    "@id": "https://example.com/active",
                    "name": "Active crumb"
                }
            }]
        }
    </script>
    | Read in 3 minutes
  • Template Routes for Single Entry / Permalink URLs

    in: Tips, Tutorials, Templating

    Single entry permalink URLs are common on the web. We’re going to apply it to a blog here, as that’s readily understood, but the pattern will work for everything else as well.

    By default ExpressionEngine’s URLs work like this https://example.com/blog/entry/url-title, where “blog” is a template group, and “entry” is a template, which is fine. But nicer would be something like https://example.com/blog/url-title, cause who needs that extra entry segment anyway? You could handle all of this in the blog template group’s index, but that’s inelegant, full of conditionals, hard to read, and hard to maintain.

    Template Routes give us an easy to create and maintain implementation. What you’ll need is the following. A blog template group with the templates index and single-entry.

    Inside of index you’ll put your blog listing, this will cover paginated archives, and categorized listings as well—URLs like https://example.com/blog/P15 and https://example.com/blog/category/cat-url-title.

    Index might look something like this:

    {exp:channel:entries channel='blog' limit='25'}
    	<div class="blog-entry">
    		<h2><a href="{route='blog/single-entry' url_title='template-routes-for-single-entry-permalink-urls'}">{title}</a></h2>
    		<p>{entry_date format='%n/%j/%Y'}</p>
    		{if has_categories}
    			<p><b>in</b>: {categories backspace='2'}<a href="{path='blog'}" title="View more in {category_name}">{category_name}</a>, {/categories}</p>
    		{/if}
    		<p>by: {author}</p>
    	</div>
    
    	{if no_results}
    		{redirect='404'}
    	{/if}
    {/exp:channel:entries}

    See that route= variable above? It’ll come into play in a moment. Next you’ll need a single-entry template that will cover your, well, single entries. And will be reached with URLs like https://example.com/blog/url-title.

    Single Entry will look something like this:

    {exp:channel:entries channel='blog' limit='1' require_entry='yes'}
    	<h1>{title}</h1>
    	<p>{entry_date format='%n/%j/%Y'}</p>
    	{if has_categories}
    		<p><b>in</b>: {categories backspace='2'}<a href="{path='blog'}" title="View more in {category_name}">{category_name}</a>, {/categories}</p>
    	{/if}
    	<p>by: {author}</p>
    
    	{blog_content}
    
    	{if no_results}
    		{redirect='404'}
    	{/if}
    {/exp:channel:entries}

    Now that you have those set up, you need to add your route. Go to Developer > Template Manager > Template Routes and set up a route for the template single-entry Give it a Route of /blog/{url_title:regex[(((?!(P\d+|category\/)).)+?)]} select “no” for “Segments Required?” then save. The regex may look a little complex, so let’s break it down. (((?!(P\d+|category\/)).)+?) uses a negative lookahead assertion, so the route does not match if the second URL segment is either pagination (P\d+) or category/. We want those URL patterns to still use the ExpressionEngine default routing to the blog/index template.

    Now in your templates replace any path variables to your single-entry URLs to use the route= variable in the first code sample. This will make sure that when clicked the visitor is sent to the right place!

    That’s it! Now go forth and clean up your ExpressionEngine URLs today!

    | Read in 3 minutes
  • SEO and User-friendly Ajax Pagination

    in: Front-end Development, Tutorials, Templating

    This article will teach you how to make SEO-friendly and user-friendly Ajax pagination using ExpressionEngine in under 10 minutes.

    Here are the goals we will accomplish with this progressive enhancement:

    • Great user experience
    • Bandwidth-friendly for mobile users
    • Minimize server-side resources
    • SEO-friendly, specifically:
      • Crawlable by all search engines
      • Proper page titles
      • Proper browser history
      • Proper 404 behavior

    In our example we are building a Really Important Website that has a fantastic list of key contacts for every important location in the universe. It’s a massive list, so we want to paginate it, and I’d like to show eight at a time. We’re going to work from the outside in, so that that layout components will make sense.

    First thing to do is to make a parent HTML layout that will also accept Ajax requests without sending or processing the whole page. We’re going to use the new variable in ExpressionEngine 3.2 to do this. Notice that I’m prefixing my layout template names with an underscore. This makes them “hidden” templates that cannot be directly accessed by a visitor; they can only be accessed when you specify them as either layouts or embeds.

    layouts/_html-layout.html

    {if is_ajax_request}
    	{layout:contents}
    {if:else}
    	<!DOCTYPE html>
    	<html>
    	<head>
    		<meta charset="utf-8">
    		<!-- Here we let templates using this layout set the title tag. -->
    		<title>{if layout:title != ''}{layout:title} | {/if}{site_name}</title>
    	</head>
    	<body>
    		<!-- Here you probably have your site nav, a page header, etc., but for
    		  for this example, I'm abbreviating the markup to only what is relevant
    		  to our Ajax pagination example.
    		-->
    
    
    		<!-- The layout contents variable will be replaced with the content of templates
    			that use this layout. We also are adding a way for templates to provide an
    			id attribute so we can hook onto this container for CSS or JavaScript.
    		-->
    		<section id="{if layout:content_id}{layout:content_id}{/if}">
    			{layout:contents}
    		</section>
    
    		<!-- The rest of your site's markup for sidebars, your footer, other scripts etc. would
    			go here. Notice that we also have a layout variable for any page-specific JavaScript
    			that a given template might need to load or provide.
    		-->
    		<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
    		{layout:js}
    	</body>
    	</html>
    {/if}

    Notice that if it’s an Ajax request, the only content we are outputting is the container contents supplied by our templates. In our case it’s going to be primarily a Channel Entries tag. We want this outer wrapper to be general purpose and reusable. We use a {layout:js} variable that also lets the templates supply needed behaviors. We could do the same with sidebars and other components that are not the same on every page of the site.

    Now let’s peel one layer of the onion back and look at our next layout template:

    layouts/_multi-layout

    As you can tell by the name, we’re now getting more specific in our layout’s purpose, in this case for multi-entry pages. Ultimately this will be used for our Managers listing page, And it is general purpose, so it can be used on other multi-entry pages as needed. It will hold our JavaScript for the Ajax pagination, defined in a {layout:set name="js"}{/layout:set} tag pair which we already told the parent _html-layout wrapper where it belongs.

    {layout='layouts/_html-layout'}
    
    <!-- Provide an id attribute for our content container. -->
    {layout:set name='content_id' value='managers-listing'}
    
    <!-- Bring forward content from our template. -->
    {layout:contents}
    
    <!-- Set the JS that all of our "Managers" content pages need. It may look scary, but
    	there are only a dozen or so lines of code, the rest are verbose comments
    	explaining the methodology.
    -->
    {layout:set name="js"}
    	<script>
    		$(document).ready(function()
    		{
    			// '#managers-listing' is the id we have provided for our content's parent container.
    			// '.managers-listing-pagination' is a class we will give to our pagination links containers.
    			// Since the content, including pagination, is replaced in the DOM by each Ajax request,
    			// we define this event handler with event delegation, watching the parent container
    			// that exists in our original markup and is not replaced or removed from the DOM.
    			$('#managers-listing').on('click', '.managers-listing-pagination a', function(e){
    				// Prevent the browser from its normal behavior when the link is clicked
    				// and grab the href of the pagination link they clicked.
    				e.preventDefault();
    				var source = $(this).attr('href');
    
    				// Add a load indicator for slow connections. Use whatever you like, if you aren't
    				// familiar with implementing them you can get some ideas at http://cssload.net
    				var loadIndicator = $('<div class="loader" id="ajax-load-indicator"></div>');
    				$('#managers-listing').prepend(loadIndicator);
    
    				// Fetch our content
    				$.get(source, function(data)
    				{
    					// Insert our new content, removing the load indicator.
    					$('#managers-listing').html(data);
    					$('#ajax-load-indicator').fadeOut('fast').remove();
    
    					// Update our page title for browser tabs, getting the page number from the pagination link they clicked.
    					// Since our pagination links exist twice in the DOM (top and bottom), we only want to grab the :first
    					// or page 3 will display "Page 33", and so on.
    					var title = 'Managers - Page ' + $('.managers-listing-pagination:first a.active').text() + ' | {site_name}';
    					document.title = title;
    
    					// For security reasons, pushState() will not update the URL if it includes a domain,
    					// so we're using regex to keep only the path, e.g. /managers/P4.
    					var path = source.replace(/https?:\/\/[^\/]+/i, '');
    
    					// Push this page onto the browser history stack for forward/back button functionality.
    					history.pushState({}, title, path);
    				});
    			});
    		});
    	</script>
    {/layout:set}

    The inline comments explain in detail what we’re doing. Basically we watch for pagination links to be clicked, fetch their content via Ajax, and make sure the page titles and the browser history are updated accordingly. That way we’ve progressively enhanced the user experience without breaking any expected browser behavior for the sake of being slick. And for search engine robots that do understand JavaScript, it’s very important for indexing.

    Now we need to create a template for the /managers URL that will use the _multi-layout. We’ll add our Channel Entries tag with pagination, and we’re done!

    managers/index

    {layout='layouts/_multi-layout'}
    
    {exp:channel:entries channel='managers' limit='8' paginate='both' orderby='title' sort='asc'}
    	{if no_results}
    		{redirect='404'}
    	{/if}
    
    	<!-- Your markup to display the entries would go here. -->
    	<h2>SEO and User-friendly Ajax Pagination</h2>
    
    	<!-- Our pagination block is below, it will be placed above and below the entries,
    		since we specified paginate="both"
    	-->
    	{paginate}
    		<!-- Set a layout variable for the page title. This is important when the full
    			page is accessed and by user agents that do not have JavaScript. For our
    			Ajax requests, we've already taken care of this in _managers-layout. Notice
    			that in this case the " | {site_name}" bit is taken care of in our
    			_html-layout where the title tag is output.
    		-->
    		{layout:set name='title'}Managers - Page {current_page}{/layout:set}
    
    		<!-- Our pagination links are in a container with the class we used in our JavaScript earlier. -->
    		<div class='managers-listing-pagination'>
    			{pagination_links}
    		</div>
    	{/paginate}
    {/exp:channel:entries}

    Again the inline comments explain what we’re doing, but I would like to draw attention to some protection we’ve added against URL fiddling or mistyped links.

    {if no_results}
    	{redirect='404'}
    {/if}

    This tells ExpressionEngine to display the site’s 404 page (with proper 404 headers), which we’ve defined in our Template Settings if there aren’t any results. When would that be? When the URL gives clues to the tag about what to show, but there aren’t any entries matching that criteria. For example, out of bounds page requests, like page 4,872 when there aren’t that many entries: /managers/P4872.

    Where to go from here?

    This simple example can be expanded upon, and of course marked up and styled to your heart’s content. The key technical elements of the implementation are:

    • Use the {paginate}{/paginate} tag pair both for pagination and to set the page title on initial load of a URL.
    • Make Ajax calls light-weight, by only processing and serving the tags necessary to deliver the content.
    • Use history.pushState() to maintain page titles and browser history both for user experience and SEO friendliness.
    • Set a 404 redirect for out of bounds requests, run a tight ship!
    • Stay DRY by using layouts and the variable so you do not have to create special Ajax templates.
    • This same technique can be directly applied to spanning a single entry across multiple pages.

    Follow these principles and you can easily deliver SEO and user-friendly Ajax pagination, impressing clients and improving the experience for their site’s visitors.

    | Read in 8 minutes
.(JavaScript must be enabled to view this email address)

ExpressionEngine News!

#eecms, #events, #releases