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!

  • Using Gmail SMTP to Send Emails from your Website

    in: Tutorials, Configuration

    Gmail’s SMTP server requirements differ from ExpressionEngine’s defaults. Google also has a few extra steps on their end to get up and running. Here’s how.

    Create an App Password for your Google Account

    If you have enabled 2-Step Verification on your Google account—which you have, right?—you will need to set up an app password that ExpressionEngine can use to authenticate. In Google, visit My Account, Sign-in & security, and under the Signing in to Google section, visit “App passwords”. Here is a direct link: https://myaccount.google.com/apppasswords.

    On the following screen, choose “Other” under “Select app”, and type in the name of your website or your domain. This will help you remember what you generated this app password for.

    Click Generate. Google will then give you the app password that you will use in ExpressionEngine. You need to copy this password to your clipboard or keep the browser window open. When you click Done, there is no way to retrieve this app password. Don’t worry if you jumped the gun and clicked Done before copying though; just revoke the app password and make another one.

    The generated app password. Copy this to the clipboard before closing the window.
    The generated app password. Copy this to the clipboard before closing the window.

    If you do not have 2-Step Verification enabled, and for some reason do not want to enable it, you will have to enable less secure sign-ins under Connected apps & sites. Toggle the switch under “Allow less secure apps” to “ON”.

    Configure Outgoing Email Settings in ExpressionEngine

    In ExpressionEngine, navigate to Settings > Outgoing Email. Change the Protocol to SMTP which will open the SMTP Options. Use the following settings:

    Server address
    smtp.gmail.com
    Server port
    465
    Username
    your.email@gmail.com
    Password
    The app password you generated earlier
    Connection type
    SSL (ssl://)

    Lastly make sure your Newline character setting on this page is set to \r\n, a requirement for Google’s SMTP servers.

    That’s it!

    You are finished and ready to start sending emails through your Gmail account. In the Developer menu, visit Utilities, and from the Communicate utility, send yourself a test email to verify that everything is working. If you get an error message, go back through the steps starting with your Google My Account page, and verify your settings.

    Caveats

    Using Gmail for website-generated emails is less than ideal. There is a sending limit of 100 emails per day via SMTP (G Suite accounts with a custom domain don’t suffer this limitation). But marketing and even transactional emails coming from a Gmail account may get flagged by email servers as spam, or even as phishing emails. This affects both the deliverability of your emails as well as your account reputation. Google has a laundry list of bulk sender guidelines, and problems might result in your Gmail account being suspended.

    So as a general rule, we do not recommend using Gmail SMTP for your website’s emails. It can be okay for your development environment, or if you just need something better than PHP mail() or sendmail for admin notifications, password reset emails, etc., and have only a handful of users. If your site has an email contact form, member registration, commenting, or discussion forums, you should use a transactional email service like:

    Marketing emails are a different category from transactional emails, which are the type triggered by a user taking an action. Registering, resetting a password, being notified of subscribed comments, and so on. For marketing and bulk email, you should use a service made for that purpose like Campaign Monitor, ConstantContact, or MailChimp. Some of the transactional providers also handle marketing emails, and mention that specifically in their marketing.

    If you are running a version of ExpressionEngine older than 3.5.0, this article still applies but you will need to set the newline and connection type manually in your system/user/config.php file.

    | Read in 4 minutes
  • Cross-Origin Resource Sharing (CORS)

    in: Tutorials

    If you’re seeing any of these things, there’s a good chance that you are experiencing a cross-origin resource failure:

    • Font-based icons (or any web font) not showing up
    • JavaScript failing in your control panel with add-ons while using the Site Manager
    • Front-end Ajax endpoint that won’t load

    In short, web browsers don’t allow sites to make requests to origins that differ from the one it is accessing. Developers are most affected by this restriction with web fonts and Ajax requests. The “origin” includes both the protocol (http://, https://) and the domain. It may not be immediately obvious that all these are different origins:

    • http://example.com
    • https://example.com
    • http://www.example.com
    • http://subdomain.example.com

    How do you know that you’re experiencing a problem with cross-origin resource sharing? If you open your developer tools in your browser, the console will have logged an error. The wording differs based on the browser, but typically will mention “Access-Control-Allow-Origin” somewhere in the message.

    Notice in the above examples that a domain with and without www are two separate origins. If this is the reason you’re experiencing a CORS issue, it is best solved by using a simple canonical redirect to either remove or force www in your URLs. This is best practice even if you aren’t having an issue with cross-origin resource sharing.

    Depending on the scenario you are facing, we will use a slightly different solution. In every case, we are adding an appropriate Access-Control-Allow-Origin header to the request. We use * if we don’t care about the requesting domain, or a specific origin if we need tighter control.

    Web Fonts Won’t Load

    Let’s say you have a @font-face declaration in your CSS, but the font is on a different origin. In that case, we will whitelist cross-origin access just for font files. In the folder where the font files live, add a .htaccess file with the following:

    # Allow font assets to be used across domains and subdomains
    <FilesMatch "\.(ttf|otf|eot|woff)$">
      <IfModule mod_headers.c>
        Header set Access-Control-Allow-Origin "*"
      </IfModule>
    </FilesMatch>

    JavaScript Errors in the Control Panel

    If you’re getting JavaScript errors in your ExpressionEngine control panel, and upon inspection see that something is not loaded due to cross-origin policy, you’ll need to add a header covering those resources. In this scenario:

    • An addon named Example is attempting to make an Ajax request to the themes folder
    • The control panel runs from https://admin.example.com
    • The Themes URL runs from https://example.com

    In the user/themes/example/ folder add a .htaccess file with the following:

    # Allow access to these theme files from https://admin.example.com
    <IfModule mod_headers.c>
      Header set Access-Control-Allow-Origin "https://admin.example.com"
    </IfModule>

    Note that we don’t have to specify https://example.com, because browsers will always allow access to resources from the same origin they live on.

    Front-end Ajax Endpoint Won’t Load

    In this scenario, you have a template that you’ve set up to act as an Ajax endpoint. We’ll assume it’s a public resource, so we will allow * origins.

    We have two different ways to do this. One is with server config, like the .htaccess we’ve used so far, and the other is to control it within your ExpressionEngine template itself. In both cases, we’re assuming a URL of https://example.com/ajax/endpoint.

    Using .htaccess

    In the web root’s .htaccess, add:

    # Allow cross-domain access to our Ajax endpoint
    <IfModule mod_headers.c>
      SetEnvIf Request_URI "/ajax/endpoint" CORS=True
      Header set Access-Control-Allow-Origin "*" env=CORS
    </IfModule>

    The first line sets an environment variable named CORS, but only for our specific URI. The second line sets the Access-Control-Allow-Origin header as normal, but the addition of env=CORS means that it will only set the header when that environment variable is set.

    This method is effective whether ExpressionEngine manages the resource, and allows regular expression URL patterns much as you’d use RewriteCond %{REQUEST_URI} with mod_rewrite.

    In an ExpressionEngine Template

    You may prefer for the cross-origin access to be set in the ExpressionEngine template itself, so another developer (including your future self) doesn’t have to hunt around to figure out how and where the header is set, or why. In this case, you can use a simple plugin to add cross-origin resource sharing to any template: Cross-Origin-Headers (plugin on GitHub).

    {exp:cross_origin_headers}

    This will allow * origins to access the content. Or to allow only a unique foreign origin:

    {exp:cross_origin_headers domain='https://subdomain.example.com'}

    Behind the scenes this extremely lightweight plugin sets the Access-Control-Allow-Origin header for us with PHP.

    Allowing Multiple Domain Origins

    One complication of the CORS implementation is that it only allows a resource to either be made fully public, or allowed to a single foreign origin. What if you have many domains (including subdomains) that need to share secure resources? You can accomplish this with server configuration, but it does have a caveat. In the folder that includes the resources that need to be shared, add the following .htaccess:

    # whitelist domains to allow CORS between, including http/https
    <IfModule mod_headers.c>
        SetEnvIf Origin "http(s)?://(example.com|subdomain.example.com)$" AccessControlAllowOrigin=$0
        Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
    </IfModule>

    Just replace example.com|subdomain.example.com in the above with a pipe-delimited list of the domains you need to allow. The first line sets an environment variable, only if the origin is in our list. The second line sets our header with the domain that we matched, only when the environment variable is set.

    What’s the caveat? Origin is a request header, and thus cannot be trusted. It works in all major browsers, so it is convenient and adds a modest amount of protection compared with *. But, a malicious user could violate your cross-origin policies by faking the Origin header. If you use this method on secure resources, make sure that they are adequately protected with server/user authentication, and do not rely soley on cross-origin browser policies.

    Further Reading

    If this topic is of interest to you, or you need to dive more deeply than the examples provided, here are some more resources for you.

    Browser Error Message Reference

    The following is a list of error messages from major browser vendors related to CORS. I’ve included it here to help you identify if that’s the issue you’re having, and to hopefully expose this article to people searching for help with these error messages.

    • Chrome / Opera
      • Access to Font at ‘https://subdomain.example.com/some-font.woff’ from origin ‘https://www.example.com’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://www.example.com’ is therefore not allowed access.
      • XMLHttpRequest cannot load https://subdomain.example.com/ajax.html. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://www.example.com’ is therefore not allowed access.
    • FireFox
      • Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://subdomain.example.com/ajax.html. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). (unknown)
      • Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://subdomain.example.com/some-font.woff. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
      • downloadable font: download failed (font-family: “Some Font” style:normal weight:500 stretch:normal src index:1): bad URI or cross-site access not allowed source: https://subdomain.example.com/some-font.woff
    • Safari
      • XMLHttpRequest cannot load https://subdomain.example.com/ajax.html. Origin https://www.example.com is not allowed by Access-Control-Allow-Origin.
    • Windows Edge
      • SEC7120: Origin https://www.example.com not found in Access-Control-Allow-Origin header. cors.html
      • SCRIPT7002: XMLHttpRequest: Network Error 0x80700013, Could not complete the operation due to error 80700013. cors.html
      • CSS3116: @font-face failed cross-origin request. No Access-Control-Allow-Origin header. some-font.woff
    | Read in 7 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
  • Content Tabs

    in: Front-end Development, Tutorials

    A tabbed UI can help save space, and better organize a content heavy page, or site section. Today I’m going to walk you through a simple way to create tabbed content in your ExpressionEngine site.

    First we need some HTML to tab!

    <div class="tabs-wrap">
    	<ul class="tabs">
    		<li><a class="act" href="" rel="t-0">Tab 1</a></li>
    		<li><a href="" rel="t-1">Tab 2</a></li>
    		<li><a href="" rel="t-2">Tab 3</a></li>
    	</ul>
    	<div class="tab-content t-0 tab-open">
    		<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    	</div>
    	<div class="tab-content t-1">
    		<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    		<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    	</div>
    	<div class="tab-content t-2">
    		<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
    	</div>
    </div>

    We use an array indexing numbering scheme, so the first tab is t-0, then t-1 and so on. tab-open indicates the default, open tab. You can move this to any tab-content, but only one tab-content may be chosen at a time. As you can see in the tabs each is assigned a rel= that matches the tab-content it should open and close.

    Now we’re going to need some light styles to give this a tabbed look. First we need a tab CSS object.

    .tabs{
    	list-style-type: none;
    	margin: 0;
    	overflow: hidden;
    	padding: 0;
    
    		li{
    			float: left;
    		}
    }
    
    .tab-content{
    	display: none;
    
    		p, ul, ol{
    			&:last-child{
    				margin-bottom: 0;
    			}
    		}
    }
    
    .tab-open{
    	display: block;
    }

    And for visuals the tab component CSS.

    .tabs{
    	margin-top: 20px;
    
    		a{
    			background-color: #F9F9F9;
    			display: inline-block;
    			line-height: 1;
    			padding: 10px;
    
    				&.act,
    				&:hover{
    					background-color: #EEEEEE;
    				}
    		}
    }
    
    .tab-content{
    	background-color: #EEEEEE;
    	margin-bottom: 20px;
    	padding: 1px 20px 20px;
    
    		.tab-content{
    			background-color: #FFFFFF;
    			margin-bottom: 0;
    		}
    
    		.tabs{
    			a{
    				&.act,
    				&:hover{
    					background-color: #FFFFFF;
    				}
    			}
    		}
    }

    And last but most !important we’ll need some javascript to make the tabs function as expected.

    ('.tabs-wrap > .tabs a').on('click',function(){
    	var tabClassIs = $(this).attr('rel');
    
    	$('.tb-act').removeClass('tb-act');
    
    	$(this)
    		.closest('ul')
    		.closest('.tabs-wrap')
    		.addClass('tb-act');
    
    	// close OTHER .tab(s), ignores the currently open tab
    	$('.tb-act > .tabs a')
    		.not(this)
    		.removeClass('act');
    
    	// removes the .tab-open class from any open tabs, and hides them
    	$('.tb-act > .tab-content')
    		.not('.tab-content.'+tabClassIs+'.tab-open')
    		.removeClass('tab-open');
    
    	$(this).addClass('act');
    
    	$('.tb-act > .tab-content.'+tabClassIs).addClass('tab-open');
    
    	// stop THIS from reloading
    	// the source window and appending to the URI
    	// and stop propagation up to document
    	return false;
    });

    You can demo and play with this code here: https://jsfiddle.net/jmathias/spktuvff/

    You can also nest tab-wrap inside a tab-content and have multiple tab-wraps per HTML document.

    That’s that, a quick and simple way to add tabbed content to your next ExpressionEngine site!

    | Read in 4 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

ExpressionEngine News

#eecms, #events, #releases