Created
March 25, 2014 11:00
Ask Ben: Displaying A Blog Teaser (Showing The First N Words)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- Save some content that might appear in a blog post. ---> | |
<cfsavecontent variable="blogContent"> | |
<p> | |
Last night, I had the <em>craziest</em> dream about | |
<strong>Tricia</strong>. It was one of those dreams where | |
you know you're dreaming beacuse it's <em>way more | |
awesome</em> than anything that's happened in real life. | |
</p> | |
<p> | |
We're standing in the locker room talking and she asks me | |
if I would mind if she changed out of her gym clothes | |
while we're talking... would <em>I mind</em>?!? I say, | |
"Not at all," trying to mask my wild enthusiasm. | |
</p> | |
</cfsavecontent> | |
<!--- | |
To display the teaser, the first thing I like to do is | |
strip out all of the formatting tags. Leaving that tags in | |
just opens yourself up to a world of complexity that you | |
don't want to deal with. | |
Let's replace the tags with a space so that we don't cause | |
any unintended word concatination (this will cause some | |
extra spacing, but there is only so much fine-tuning we | |
can do in such a generic process). | |
---> | |
<cfset blogContent = reReplace( | |
blogContent, | |
"</?\w+(\s*[\w:]+\s*=\s*(""[^""]*""|'[^']*'))*\s*/?>", | |
" ", | |
"all" | |
) /> | |
<!--- | |
Now, let's minimize any white space characters. Technically, | |
since HTML doesn't render more than one white space character, | |
this isn't an issue; but, it's something I like to do anyway | |
for intent. | |
---> | |
<cfset blogContent = reReplace( | |
trim( blogContent ), | |
"\s+", | |
" ", | |
"all" | |
) /> | |
<!--- | |
Now that we have cleaned our blog content, we need to extract | |
the first 50 words. While this might not seem like a pattern, | |
I think using a regular expression for this is a very nice | |
approach. Think of it this way: the first 50 words is like | |
finding a pattern that is 50 instances long. | |
For ease-of-use, we are going to define a word TOKEN as any | |
non-spaces followed by a space. | |
NOTE: Because regular expressions are, by default, greedy, | |
the notation {1,50} will try to match all 50 word tokens | |
before it tries to match anything less. | |
---> | |
<cfset blogSnippets = reMatch( | |
"([^\s]+\s?){1,50}", | |
blogContent | |
) /> | |
<!--- | |
As a safe-guard, let's double-check to make sure that the | |
reMatch() method returned at least one match. If a blog entry | |
was purely visual (ie. just an image or set of images), then | |
no words would be returned. If that is the case, then let's | |
make up a content description. | |
---> | |
<cfif !arrayLen( blogSnippets )> | |
<!--- | |
Fabricate content snippets. This is an outlier case, but | |
we want to do this for mathematical stability. | |
---> | |
<cfset blogSnippets = [ "Visual content only" ] /> | |
</cfif> | |
<!--- | |
Since reMatch() returns an array of matches, we have now | |
broken our entire blog content up into an array in which | |
each index has 50 or less words. As such, we can use the | |
first index value as our blog teaser. | |
But, since we broke up the entire blog post, we can also | |
now estimate the total number of words in the entire blog | |
entry (if we want to output it). | |
We can even go one step further and give the reader an | |
approciate time investment they would have to make based on | |
an average number of words per minute. | |
---> | |
<cfoutput> | |
<!--- | |
Get the approximate word count. The first N-1 matches | |
will be 50; then, we can use the last returned match as | |
a space-delimitted list. | |
---> | |
<cfset wordCount = ( | |
(50 * (arrayLen( blogSnippets ) - 1)) + | |
listLen( blogSnippets[ arrayLen( blogSnippets ) ], " " ) | |
) /> | |
<!--- | |
Get the estimated time requirement for reading this blog | |
entry - this assumes that a user can read about 200 words | |
per minutes. This can be much higher, but for our | |
purposes, this is fine. | |
NOTE: Our max here is to make sure the read time never | |
goes below one minute. | |
---> | |
<cfset readingTime = max( round( wordCount / 200 ), 1 ) /> | |
<p> | |
<!--- | |
The first snippet of 50 words will serve as our | |
blog teaser | |
---> | |
#blogSnippets[ 1 ]# | |
<span style="white-space: nowrap ;"> | |
<!--- Words and time estimates. ---> | |
[<em>#wordCount# words / | |
aprx. read time: #readingTime# minute(s)</em>] | |
… <a href="##">read more</a> » | |
</span> | |
</p> | |
</cfoutput> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
<html> | |
<head> | |
<title>Displaying A Content Teaser With jQuery</title> | |
<script type="text/javascript" src="jquery-1.3.2.js"></script> | |
<script type="text/javascript"> | |
// Let's create a jQuery plugin that creates content | |
// teasers with word counts and time estimates. | |
$.fn.teaser = function( teaserOptions ){ | |
// Create the internal options. | |
var options = $.extend( | |
{}, | |
$.fn.teaser.options, | |
teaserOptions | |
); | |
// Iterate over each item in this collection such | |
// that each teaser can be applied individually based | |
// on targeted information. | |
this.each( | |
function(){ | |
var container = $( this ); | |
// First, we want to wrap the contents of the | |
// container in a new DIV so that we can hide | |
// it in lieu of our teaser. | |
var fullContent = container.children() | |
.wrapAll( | |
"<div class=\"full-content\"></div>" | |
) | |
.parent() | |
; | |
// Now, let's get all the text from the | |
// content container so that we can start | |
// constructing our teaser. | |
var rawContent = $.trim( fullContent.text() ); | |
// Remove any extra white space. | |
rawContent = rawContent.replace( | |
new RegExp( "\\s+", "g" ), | |
" " | |
); | |
// Break the content up into chunks based on | |
// the words count. | |
var snippets = rawContent.match( | |
new RegExp( | |
"([^\\s]+\\s?){1," + options.wordCount + "}", | |
"g" | |
) | |
); | |
// Check to make sure there is at least one | |
// snippet item. | |
if (!snippets.length){ | |
snippets = [ "Visual content only" ]; | |
} | |
// Calculate the word count. | |
var wordCount = ( | |
(options.wordCount * (snippets.length - 1)) + | |
snippets[ snippets.length - 1 ].split( " " ).length | |
); | |
// Calculate the reading time. | |
var readingTime = Math.max( | |
Math.floor( wordCount / options.wordsPerMinute ), | |
1 | |
); | |
// Now, let's create the teaser. We will simply | |
// append it to the original container. | |
container.append( | |
"<p class=\"teaser-content\">" + | |
snippets[ 0 ] + | |
" [<em>" + | |
(wordCount + " words / aprx. read time: ") + | |
(readingTime + " minute(s)") + | |
"</em>]" + | |
"... <a href=\"#\">read more</a>" + | |
"</p>" | |
); | |
// Hide the actual content. | |
fullContent.hide(); | |
} | |
); | |
// Now that we have split up the content and created | |
// the teaser headers, let's hook up the read more | |
// links to display the content. | |
this.find( "p.teaser-content a" ) | |
.attr( "href", "javascript:void( 0 )" ) | |
.click( | |
function(){ | |
var link = $( this ); | |
// Hide the teaser. | |
link.parent().hide(); | |
// Show the content. | |
link.parent().prev().show(); | |
// Cancel default event. | |
return( false ); | |
} | |
) | |
; | |
// Return this collection for method chaining. | |
return( this ); | |
} | |
// Define defaults for the content teaser plugin. | |
// These can be overridden using the options hash when | |
// calling the teaser method. | |
$.fn.teaser.options = { | |
wordCount: 50, | |
wordsPerMinute: 200 | |
}; | |
// --------------------------------------------------- // | |
// --------------------------------------------------- // | |
// When the DOM is ready, initialize. | |
$(function(){ | |
$( "div.blog-entry" ).teaser(); | |
}); | |
</script> | |
</head> | |
<body> | |
<h1> | |
Displaying A Content Teaser With jQuery | |
</h1> | |
<div class="blog-entry" style="border-bottom: 1px solid black ;"> | |
<p> | |
Last night, I had the <em>craziest</em> dream about | |
<strong>Tricia</strong>. It was one of those dreams where | |
you know you're dreaming beacuse it's <em>way more | |
awesome</em> than anything that's happened in real life. | |
</p> | |
<p> | |
We're standing in the locker room talking and she asks me | |
if I would mind if she changed out of her gym clothes | |
while we're talking... would <em>I mind</em>?!? I say, | |
"Not at all," trying to mask my wild enthusiasm. | |
</p> | |
</div> | |
<div class="blog-entry" style="border-bottom: 1px solid black ;"> | |
<p> | |
Last night, I had the <em>craziest</em> dream about | |
<strong>Tricia</strong>. It was one of those dreams where | |
you know you're dreaming beacuse it's <em>way more | |
awesome</em> than anything that's happened in real life. | |
</p> | |
<p> | |
We're standing in the locker room talking and she asks me | |
if I would mind if she changed out of her gym clothes | |
while we're talking... would <em>I mind</em>?!? I say, | |
"Not at all," trying to mask my wild enthusiasm. | |
</p> | |
</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment