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!

  • 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 {is_ajax_request} 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 {is_ajax_request} 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
  • DRY Pagination

    in: Tips, Templating

    If you want quick, easy to maintain, and consistent pagination across your site or application, DRY (Don’t Repeat Yourself) is a great way to accomplish this. in ExpressionEngine you can create DRY pagination in a few simple steps.

    First you’ll need a Template Partial. I like to name this par-paginate-links.

    {paginate}
    	{pagination_links page_padding='1'}
    		<div class="pagination">
    			<ul>
    				{first_page}
    					<li class="pg-first"><a href="{pagination_url}"><span>first</span></a></li>
    				{/first_page}
    				{previous_page}
    					<li class="pg-prev"><a href="{pagination_url}"><span>prev</span></a></li>
    				{/previous_page}
    				{page}
    					<li><a href="{pagination_url}"{if current_page} class="act"{/if}>{pagination_page_number}</a></li>
    				{/page}
    				{next_page}
    					<li class="pg-next"><a href="{pagination_url}"><span>next</span></a></li>
    				{/next_page}
    				{last_page}
    					<li class="pg-last"><a href="{pagination_url}"><span>last</span></a></li>
    				{/last_page}
    			</ul>
    		</div>
    	{/pagination_links}
    {/paginate}

    This partial covers paginated links, you can adjust the output to your personal needs, but this is your basic “first, previous, 1, 2, 3, next, last” pattern.

    Now that you have this, all you need to do is call it into the exp:channel:entries tags you want to paginate.

    Something like this,

    {exp:channel:entries channel='blog' limit='10'}
    	HTML Output...
    	{if no_results}
    		{redirect='404'}
    	{/if}
    	{par-paginate-links}
    {/exp:channel:entries}

    And there you have, a clean, fast and simple way to make your websites more maintainable with ExpressionEngine.

    | Read in 2 minutes

ExpressionEngine News

#eecms, #events, #releases