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!

  • 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
  • Image Grid

    in: Front-end Development, Tips

    Here’s a quick tip for creating a equal sized image grid in HTML and CSS. There are two legit ways to accomplish this pattern, and both ways use the same HTML. I’ve limited the output to eight items for example purposes, but this technique will theoretically work for infinity items.

    <div class="collection">
    	<ul class="img-grid">
    		<li>
    			<figure>
    				<img src="https://expressionengine.com/asset/img/learn/example.png">
    			</figure>
    		</li>
    		<li>
    			<figure>
    				<img src="https://expressionengine.com/asset/img/learn/example.png">
    			</figure>
    		</li>
    		<li>
    			<figure>
    				<img src="https://expressionengine.com/asset/img/learn/example.png">
    			</figure>
    		</li>
    		<li>
    			<figure>
    				<img src="https://expressionengine.com/asset/img/learn/example.png">
    			</figure>
    		</li>
    		<li>
    			<figure>
    				<img src="https://expressionengine.com/asset/img/learn/example.png">
    			</figure>
    		</li>
    		<li>
    			<figure>
    				<img src="https://expressionengine.com/asset/img/learn/example.png">
    			</figure>
    		</li>
    		<li>
    			<figure>
    				<img src="https://expressionengine.com/asset/img/learn/example.png">
    			</figure>
    		</li>
    		<li>
    			<figure>
    				<img src="https://expressionengine.com/asset/img/learn/example.png">
    			</figure>
    		</li>
    	</ul>
    </div>

    I’ve used a div.collection as a wrapper which is unnecessary for this example, but is a good practice. Allowing you to have multiple grids in a single HTML document that can be targeted individually.

    I’m a big proponent of ITCSS, and an object oriented approach to CSS. Here is the object LESS for this grid.

    .img-grid{
    	list-style-type: none;
    	margin: 0;
    
    		li{
    			margin-bottom: 20px;
    		}
    
    		figure{
    			margin: 0;
    
    				img{
    					max-width: 100%;
    				}
    		}
    }
    
    // responsibility
    @media screen and (min-width: 414px){
    	.img-grid{
    		margin: 0 -10px;
    		overflow: hidden;
    
    			li{
    				box-sizing: border-box;
    				float: left;
    				padding: 0 10px;
    				width: 50%;
    			}
    	}
    }
    
    @media screen and (min-width: 750px){
    	.img-grid{
    		li{
    			width: 33.33%;
    		}
    	}
    }
    
    @media screen and (min-width: 1200px){
    	.img-grid{
    		li{
    			width: 25%;
    		}
    	}
    }

    This gives us a single column stack, that expands to two columns, then three and finally four columns at max width. You can adjust the math to work however you like, but I’ve found this to be pretty universally useful, and visually pleasing at all breakpoints.

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

    But this is old school code I hear you cry! Well, sure, I suppose. If you prefer cutting edge you can accomplish the same exact grid with this flex-box code instead.

    .img-grid-flex{
    	display: flex;
    	flex-wrap: wrap;
    	list-style-type: none;
    	margin: 0;
    
    		li{
    			box-sizing: border-box;
    			// grow shrink basis
    			flex: 0 1 100%;
    			// allows incomplete rows to grow as needed.
    			// flex: 1 0 100%;
    			margin-bottom: 20px;
    		}
    
    		figure{
    			margin: 0;
    
    				img{
    					max-width: 100%;
    				}
    		}
    }
    
    // responsibility
    @media screen and (min-width: 414px){
    	.img-grid-flex{
    		margin: 0 -10px;
    
    			li{
    				flex-basis: 50%;
    				padding: 0 10px;
    			}
    	}
    }
    
    @media screen and (min-width: 750px){
    	.img-grid-flex{
    		li{
    			flex-basis: 33.33%;
    		}
    	}
    }
    
    @media screen and (min-width: 1200px){
    	.img-grid-flex{
    		li{
    			flex-basis: 25%;
    		}
    	}
    }

    As you can see the code isn’t much different, just another way to accomplish it. The upside of display: flex is that you don’t need to use floats, or float clearing methods. Additionally, you can have flex box do the math for you and display incomplete rows with larger images. You just need to change flex: 0 1 100%; to flex: 1 0 100%;.

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

    So there you have it. A nice simple Image grid you can use on your ExpressionEngine CMS site today!

    | 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
  • Responsive Videos

    in: Front-end Development, Tips

    We all LOVE videos! But they can be a real difficult thing to do responsively. So here is some code to help you make videos responsive in your next ExpressionEngine CMS site.

    The HTML

    <figure class="video">
    	<div class="video-player">
    		<iframe src="//player.vimeo.com/video/39394380?color=f0a400" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
    	</div>
    	<figcaption><a href="http://leihu.com">James Mathias</a> &mdash; Made By Few</figcaption>
    </figure>

    I use a figure, so that I can add a caption, the important part is the div.video-player around the iframe. Here I use a class of .video on the figure as a best practice for potential CSS targeting.

    Speaking of CSS, here is the LESS object.

    .video-player{
    	height: 0;
    	overflow: hidden;
    	padding-bottom: 56.25%; /* 16/9 ratio */
    	padding-top: 30px; /* IE6 workaround */
    	position: relative;
    
    		// set dimensions and position of elements inside .video-player
    		embed, iframe, img, object, video{
    			height: 100%;
    			left: 0;
    			position: absolute;
    			top: 0;
    			width: 100%;
    		}
    }

    That’s all you need. Remember don’t put a width or height attribute on the iframe, the browser will do this math for you!

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

    Now go fill your ExpressionEngine CMS sites with videos, to the brim!

    | Read in 2 minutes
  • Toggling Content

    in: Front-end Development, Tips

    Occasionally you have some information that is needed, but not the most important, and you may want to hide it away, and let the visitor decide when they want to see it. So you toggle that content! Here’s a simple and fast way to add this functionality to your website, it’s reusable and can appear multiple times in a single HTML document.

    Let’s start with the JavaScript. I used jQuery.

    $(function(){
    
    	$('.toggle-link').on('click',function(e){
    		var txtIs = $(this).attr('data-open');
    		var hideIs = $(this).attr('data-close');
    		var objIs = $(this).attr('data-rel');
    		var toggleObj = $('[data-rev='+objIs+']');
    
    		if($.trim($(this).text()) === txtIs){
    			$(this).text(hideIs);
    		}
    		else{
    			$(this).text(txtIs);
    		}
    
    		toggleObj.toggleClass('toggle-open');
    
    		// stop THIS href from loading
    		// in the source window
    		e.preventDefault();
    	});
    
    }); // close (document).ready

    This will listen for .toggle-link clicks and then open and close (toggle) the proper .toggle-content for you.

    Here is the HTML

    <h1>Content Header <a href="#" class="toggle-link" data-rel="toggle-test" data-close="Hide" data-open="Show">Show</a></h1>
    <div class="toggle-content" data-rev="toggle-test">
    	<p>This content is hidden, until the toggle-link is clicked.</p>
    </div>

    The important bit to remember here is that the values of data-rel and data-rev need to match, and need to be unique from any other pairs in the same HTML document.

    The value of data-close determines what the link’s text will be when the content is visible. And the value of data-open determines the text when the content is hidden. The text in the link should match the value of data-open.

    And lastly, just a little CSS to get our open and closed states.

    .toggle-content{
    	display: none;
    }
    
    .toggle-open{
    	display: block;
    }

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

    So there you have it another tool for your next ExpressionEngine CMS site.

    | Read in 2 minutes

ExpressionEngine News

#eecms, #events, #releases