Author: Derek Jones

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!

  • 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>Derek Jones</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
  • 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
  • Easy Multi-device Local Development with xip.io

    in: Tips

    You are building a site locally and you’d like to quickly check your work on your iPad, iPhone, another computer, or maybe a client’s laptop. You could pay for a dynamic DNS service, setup a DNS server on your local network, or setup a proxy server perhaps. But there’s an easier way. The key is a “magic domain” service, xip.io, which lets you give meaningful names to your local IP addresses.

    In this example, we will make my local https://awesomesite.dev available to others on my network, including my client’s iOS devices.

    How to do it

    I have my Apache VirtualHost and /etc/hosts file on my iMac configured to use https://awesomesite.dev/ for local development. I’ll add the special xip.io address as a ServerAlias, using the local IP address of my iMac (10.0.1.4):

    ServerName awesomesite.dev
    ServerAlias awesomesite.dev.10.0.1.4.xip.io
    DocumentRoot /Users/derek/Sites/AwesomeSite

    I’m keeping the awesomesite.dev. prefix so I can use this naming system with all my projects and make it clear which VirtualHost I’m accessing. If you’re using MAMP Pro, just go to your Hosts tab and click the + button under the “Aliases” box to add the xip.io address as an alias. Notice that xip.io addresses follow the simple pattern of your domain name, followed by your local IP address, followed by xip.io.

    Now to make sure that links and asset requests go to the right URLs, at the bottom of my config.php file in ExpressionEngine, I add the following:

    if (preg_match('/.*\.xip\.io$/', $_SERVER['HTTP_HOST']))
    {
    	$config['base_url'] = "http://{$_SERVER['HTTP_HOST']}/";
    }

    This block of code tells ExpressionEngine to use the xip.io address as the basis for URLs, but only if the request came from a .xip.io address. The preg_match() conditional validates the HTTP_HOST header for us in this case.

    Use the special {base_url} variable in all of your URL settings which keeps your site ultra-portable. If you do not, then you will need to override site_url, cp_url, theme_folder_url, and upload_preferences for each Upload Destination.

    Never use HTTP_HOST without validating it! It is a request variable, which means it can be faked, and you can’t trust it. If you use it without validation, you run the risk of a cache poisoning attack. That means an attacker could cause all generated links to point to their own servers for people who access your site after they do. If they copy your design, they have an effective phishing attack on your site’s visitors. Always validate the HTTP_HOST header before using it! If you’re using a multi-environment config, make sure yours does this! Exclamation mark!

    Violá

    Make sure to restart your web server after updating your VirtualHost, then anytime you want to pull up your local site on any device on your network, just key the xip.io address into the browser, in my case https://awesomesite.dev.10.0.1.4.xip.io. Then I can work on my iMac and check it instantly on my phone, iPad, or Macbook Air, or let a client spin it up on their device.

    There are many ways to do this; xip.io is just one of the easy and seamless options.

    | Read in 4 minutes
  • 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
  • Blog Channel Set

    in: Resources, Channel Sets

    Quickly create a Blog Channel for ExpressionEngine with this Channel Set.

    The Blog Channel Set comes with custom fields, statuses, and categories to get you up and running fast. Further customizations can be made to tailor your blog, but here’s what’s inside:

    Custom Fields

    • {blog_content}: a Textarea for the blog post’s content
    • {blog_image}: a File field for a featured image for the blog post

    Statuses

    • Open: published
    • Closed: not published
    • Featured: to call attention to a blog post, typically on the homepage

    Categories

    • News
    • Personal
    • Photos
    • Videos
    • Music

    Sample Tags

    {exp:channel:entries channel='blog' limit='1' require_entry='yes'}
    	{if no_results}
    		{redirect='404'}
    	{/if}
    
    	<h1>Derek Jones</h1>
    
    	{if has_categories}
    		<div>Categories:
    			<ul>
    				{categories}
    					<li><a href="{path='blog/index'}">{category_name}</a></li>
    				{/categories}
    			</ul>
    		</div>
    	{/if}
    
    	{if blog_image}
    		{blog_image}
    			<figure>
    				<img src="{blog_image:image}" alt="{blog_image:caption}">
    				<figcaption>{blog_image:caption}</figcaption>
    			</figure>
    		{/blog_image}
    	{/if}
    
    	{blog_content}
    {/exp:channel:entries}
    | Read in 1 minute

ExpressionEngine News

#eecms, #events, #releases