Create a “load more” widget using PHP, Ajax, Mootools, Bootstrap and Mustache.js

3 years ago David Walsh created an awesome (his words, but I agree!) NetTuts post called Create a Twitter-Like “Load More” Widget which is still today a reference on learning how to create “load more” widgets.

Today, with the emergence of projects such as Bootstrap (that took the web by a storm) and Mustache.js creating such a widget is getting easier.

Prerequisite

This tutorial requires several prerequisite in order to be completed successfully:

  • The server needs to run PHP5 to be able to use JSON functions.
  • The widget does not need any custom CSS we will only use the famous Bootstrap Framework.
  • We will use the lastest version of Mootools Core (1.4.5) and the Fx.Scroll Class from Mootools More.
  • To finish we will need to use the JavaScript version (Mustache.js) of the great Mustache project.

Step 1: PHP/MySQL

The PHP part is more or less what David Walsh created.

Start session

By using session the widget will remember how many posts you loaded during the last visit (in case you open a new page and come back).$_SESSION['posts_start'] is initialised and represent the number of posts already loaded in the page (by default two posts are directly loaded).

/* settings */
session_start();
$number_of_posts = 2; //2 at a time
$_SESSION['posts_start'] = $_SESSION['posts_start'] ? $_SESSION['posts_start'] : $number_of_posts;

Get posts from the Db

The function get_posts has two optional parameters ($start is increased every time you push the “more” button) to get only the posts you wish for.

/* grab stuff */
function get_posts($start = 0, $number_of_posts = 2) {
	/* connect to the db */
	$connection = mysql_connect('localhost','username','password');
	mysql_select_db('db_name',$connection);
	$posts = array();
	/* get the posts */
	$query = "SELECT post_title, post_content, post_name, post_date, ID FROM wp_posts WHERE post_status = 'publish' ORDER BY post_date DESC LIMIT $start,$number_of_posts";
	$result = mysql_query($query);
	while($row = mysql_fetch_assoc($result)) {
		// Remove html tag and truncate the content
		$row['post_content'] = substr(strip_tags($row['post_content']), 0,200) . '...';
		$posts[] = $row;
	}
	/* return the posts in the JSON format */
	return json_encode($posts);
}

Set up the JSON API

The purpose of the following piece of code is to return the desired posts in a JSON format for our widget convenience. If the $_GET['start'] variable is set in the requested URL then we know it has been requested from our widget(via AJAX request). It will then return posts and die.

You could also use the great JSON API plugin

/* loading of stuff */
if(isset($_GET['start'])) {
	/* spit out the posts within the desired range */
	$posts = get_posts($_GET['start'],$_GET['desiredPosts']);
	// decode the result to know the exact length of the result
	// It could be 2, 1 or 0 in this case
	$postsDecoded = json_decode($posts);
	// If the result is not empty we update the session
	if (!empty($postsDecoded))
	{
		/* save the user's "spot", so to speak */
		$_SESSION['posts_start']+= count($postsDecoded);
	}
	echo get_posts($_GET['start'],$_GET['desiredPosts']);
	/* kill the page */
	die();
}

Step 2: HTML (Bootstrap markups)

Our widget HTML is fairly simple. It contains a title with a posts counter (#widget .badge),a posts container (#widget .content) and a more button (#widget .more).

<div id="widget">
	<h4 id="widget-title">
		Widget Title <span class="badge badge-info"><?php echo $_SESSION['posts_start'] ?>
		</span>
	</h4>
	<div class="content" style="height: 300px; overflow: auto; margin: 0px;"></div>
	<button class="more btn btn-block">
		More <span class="caret"></span>
	</button>
</div>

Step 3: Mustache template (Bootstrap markups)

The mustache template contains the HTML for posts. The block {{#data}} YOUR HTML {{/data}} is rendered once for each item in the list ({data:[]} see below in the JavaScript part).

The inverted section opens with {{^data}} instead of {{#data}}. The block of an inverted section is rendered only if the value of that section’s tag is null, undefined, false, or an empty list.

For more details please check the Mustache.js documentation.

{{#data}}
<div class="item">
	<img class="thumbnail pull-left" width="50px" height="50px" src="http://placehold.it/50x50" style="margin: 0px 10px 5px 0px;">
	<a href="#">
		<h4 style="margin: 5px 0px;">{{post_title}}</h4>
	</a>
	<p>
		<small>{{post_content}}</small>
	</p>
	<div class="well well-small">
		{{post_date}}
	</div>
</div>
{{/data}}
{{^data}}
	<div class="alert alert-error">No data</div>
{{/data}}

Step 4: Mootools JavaScript

Initialize variables

  • The start variable represent the number of posts already loaded in the page.
  • The initialPosts variable is an array containing posts (either the default number of posts or the last number of posts saved in the session).
  • The desiredPosts variable is here 2 (See the PHP part above).
  • The template variable is either NULL or contains the Mustache template String.
var start = <?php echo $_SESSION['posts_start']; ?>;
var initialPosts = <?php echo get_posts(0,$_SESSION['posts_start']);  ?>;
var desiredPosts = <?php echo $number_of_posts; ?>;
var template = null;

DOM elements

// Widget element
var widget = $('widget'),
// Element to load the posts
content = widget.getElement('.content'),
// the more button
more = widget.getElement('.more'),
// the post counter
counter = widget.getElement('.badge');

Post Handler

The postHandler function purpose is to inject posts into our HTML.

The first execution loads and stores the Mustache template file (via a synchronous Ajax request) then uses the function Mustache.render(template, {'data' : data}) (that returns a HTML String of our posts) to populate the widget HTML.

Next executions (when clicking the “more” button) skip the Ajax request on the template and simply populate the widget HTML.

var postHandler = function(data){
	// Check if the template is already stored
	if (!template){
		// If not we get it
		new Request({
			url: 'load-more.mustache',
			method: 'get',
			async: false,
			onSuccess: function(responseText){
				// the response text is stored as the template
				template = responseText;
			},
			onFailure: function() {
				// insert the failure message
				widget.grab(alerts.templateFailure,'before');
				// get rid of the widget
				widget.dispose();
			}
			// Send the Ajax request
		}).send();
	}
	else{
		// Set the progress bar to 100%
		progressBar.setStyle('width', '100%');
		// Delay the normal more button to come back for a better effect
		more.set.delay(500, more, ['html','More <span class="caret"></span>']);
	}
	// Transform the template (String) into Elements that we can use
	var childrens =  new Element('div', {
		// Mustache requires an object property to reference in order to
		// create loops
		'html' : Mustache.render(template, {'data' : data})
	}).getChildren('*');
	// insert childrens at the end of the content element
	content.adopt(childrens);
	// Scroll to the first element loaded
	scroll.toElement(childrens[0]);
}

Ajax request to get the data

This part of code (I kept only the logic and removed effects) create a Ajax request instance for the more button to use when clicked. We ask the same page to return raw data (JSON) and on success event we either load this data into our template (if responseJSON.length > 0) or remove the more button (no more data to load).

// create the data Ajax request
var request = new Request.JSON({  
	url: 'load-more.php', // ajax script -- same page
	method: 'get',
	// Any calls made to start while the request is running will be ignored.
	link: 'ignore',
	// We do not want IE to cache the result
	noCache: true,
	onSuccess: function(responseJSON) {
		// Check if data is returned
		if (responseJSON.length > 0){
			// Update the total number of items
			start += responseJSON.length;
			// load items on the page
			postHandler(responseJSON);
		}
		else{
			// Remove the more button
			more.dispose.delay(500,more);
		}
	}
});

Add the click event to the “more” button

// add the click event to the more button
more.addEvent('click',function(){  
	// begin the ajax attempt
	request.send({
		data: {  
			'start': start,  
			'desiredPosts': desiredPosts  
		} 
	});  
});

Complete JavaScript

I extensively commented the following code to be as clear as possible. If you are having problems to understand just leave a comment, I will get back to you.

//domready event  
window.addEvent('domready',function() {
	// initialize variables
	var start = <?php echo $_SESSION['posts_start']; ?>;
	var initialPosts = <?php echo get_posts(0,$_SESSION['posts_start']);  ?>;
	var desiredPosts = <?php echo $number_of_posts; ?>;
	// either null or contains the mustache template
	var template = null;
	// Widget element
	var widget = $('widget'),
	// Element to load the posts
	content = widget.getElement('.content'),
	// the more button
	more = widget.getElement('.more'),
	// the post counter
	counter = widget.getElement('.badge');
	// Create alerts elements (Display Success or Failure)
	var alerts = {
			templateFailure : new Element('div',{'class' : 'alert alert-error','html' : 'Could not get the template.'}),
			requestEmpty : new Element('div',{'class' : 'alert alert-info','html' : 'No more data'}),
			requestFailure : new Element('div',{'class' : 'alert alert-error','html' : 'Could not get the data. Try again!'})
	}
	// create the Bootstrap progress bar element
	var progressElement = new Element('div', {
		'class': 'progress',
		'html': "<div class='bar'></div>",
		'styles': {
			'margin-bottom' : 0
		}
	});
	var progressBar = progressElement.getElement('.bar');
	// Create a scroll instance on the widget content
	// This Class is included in Mootools More
	var scroll = new Fx.Scroll(content, {
		duration: 1000,
		wait: false
	});
	// function that handle posts
	var postHandler = function(data){
		// Check if the template is already stored
		if (!template){
			// If not we get it
			new Request({
				url: 'load-more.mustache',
				method: 'get',
				async: false,
				onSuccess: function(responseText){
					// the response text is stored as the template
					template = responseText;
				},
				onFailure: function() {
					// insert the failure message
					widget.grab(alerts.templateFailure,'before');
					// get rid of the widget
					widget.dispose();
				}
				// Send the Ajax request
			}).send();
		}
		else{
			// Set the progress bar to 100%
			progressBar.setStyle('width', '100%');
			// Delay the normal more button to come back for a better effect
			more.set.delay(500, more, ['html','More <span class="caret"></span>']);
		}
		// Transform the template (String) into Elements that we can use
		var childrens =  new Element('div', {
			// Mustache requires an object property to reference in order to
			// create loops
			'html' : Mustache.render(template, {'data' : data})
		}).getChildren('*');
		// insert childrens at the end of the content element
		content.adopt(childrens);
		// Scroll to the first element loaded
		scroll.toElement(childrens[0]);
	}
	// place the initial posts in the page
	postHandler(initialPosts);

	// create the data Ajax request
	var request = new Request.JSON({  
		url: 'load-more.php', // ajax script -- same page
		method: 'get',
		// Any calls made to start while the request is running will be ignored.
		link: 'ignore',
		// We do not want IE to cache the result
		noCache: true,  
		onRequest: function() {
			// Set the progress bar to 0%
			progressBar.setStyle('width', '0%');
			// remove the more button innerHTML and insert the progress bar
			more.empty().grab(progressElement);
		},
		onSuccess: function(responseJSON) {
			// Check if data is returned
			if (responseJSON.length > 0){
				// Update the total number of items
				start += responseJSON.length;
				// Update the counter
				counter.set('html', start);
				// load items on the page
				postHandler(responseJSON);
			}
			else{
				// insert the empty message
				widget.grab(alerts.requestEmpty,'before');
				// Set the progress bar to 100%
				progressBar.setStyle('width', '100%');
				// Remove the more button
				more.dispose.delay(500,more);
				// remove the empty message after 4 seconds
				alerts.requestEmpty.dispose.delay(4000,alerts.requestEmpty);
			}
		},
		onFailure: function() {
			// insert the failure message
			widget.grab(alerts.requestFailure,'before');
			// Set the progress bar to 100%
			progressBar.setStyle('width', '100%');
			// Delay the normal more button to come back for a better effect
			more.set.delay(500, more, ['html','More <span class="caret"></span>']);
		}
	});
	// add the click event to the more button
	more.addEvent('click',function(){  
		// begin the ajax attempt
		request.send({
			data: {  
				'start': start,  
				'desiredPosts': desiredPosts  
			} 
		});  
	});
});

Create a JavaScript Framework using Mootools – Part 5: Compress your Framework

Compress your Framework

Now that you have your Framework create in yourlibrary.js, you might want to save some space using a compressor. I will present two famous one that I used before. First step is to create a compressor folder.

Framework tree so far:

YourFramework/
–|-mootools-core/
–|-mootools-more/
–|-yourLibrary/
–|-packager/
–|-compressor/
–|-yourlibrary.js

JsMin

git clone https://github.com/rgrove/jsmin-php

Go get the latest version of jsmin.php here: and copy it inside the compressor folder.

Open your build.php file and add the highlighted rows:

<?php
// include the packager library
require dirname(__FILE__) . '/packager/packager.php';

// include the compressor library
require dirname(__FILE__) . '/compressor/jsmin.php';

// List of Package needed
$packages = array('mootools-core', 'mootools-more', 'yourLibrary');

// Register all the packages needed to create your Framework
$pkg = new Packager($packages);

// List of Component needed for Core. These are just examples, you might need more
$core = array('Core/Core', 'Core/Array', 'Core/String');

// List of Component needed for More. These are just examples, you might need more
$more = array('More/Fx.Accordion', 'More/URI', 'More/Fx.Scroll');

// List of Component needed for More
$yourlibrary = array('yourLibrary/MyFirstClass', 'yourLibrary/MySecondClass');

// List of all Component needed
$components = array_merge($core, $more, $yourlibrary);

// Create the yourlibrary.js file
$pkg->write_from_components('yourlibrary.js', $components);

// create a compressed version of yourlibrary.js
file_put_contents('yourlibrary_compressed.js', JSMin::minify(file_get_contents('yourlibrary.js')));
?>

–Or–

YUI Compressor

Yahoo UI Compressor is an open source tool that supports the compression of both JavaScript and CSS files. In addition to removing comments and white-spaces, YUI Compressor obfuscates local variables using the smallest possible variable name. This obfuscation is safe, even when using constructs such as ‘eval’ or ‘with’ (although the compression is not optimal is those cases) Compared to jsmin, the average savings is around 20%.

Go get the latest version here: https://github.com/yui/yuicompressor/blob/master/build/ and copy it inside the compressor folder.

Usage: java -jar yuicompressor-x.y.z.jar [options] [inputfile]

YUI Compressor doc

We are now done with this tutorials in 5 parts. I hope it was helpful. Any questions, comments let me know.

Create a JavaScript Framework using Mootools – Part 4: Use Packager

Use Packager

“Packager is a PHP 5.2+ library to concatenate libraries split in multiple files in a single file. It automatically calculates dependancies. Packager requires a yml header syntax in every file, and a package.yml manifest file, as seen on the MooTools project.”

Clone Packager using git command line:

git clone https://github.com/kamicane/packager.git

Framework tree so far:

YourFramework/
–|-mootools-core/
–|-mootools-more/
–|-yourLibrary/
–|-packager/
—-|-packager (Command line file)
—-|-packager.php (Core Library)

Update your .yml file

Open your .yml file: YourFramework/yourLibrary/package.yml and insert the required properties:

name: yourLibrary
sources:
 - "Source/MyFirstClass.js"

Name is the “Package” name used by Packager to reference your files.
Sources is the list of Classes you want your Framework to reference.

Update MyFirstClass.js

You now know that in order for the packager to work you will need to add yml header syntax in every Classes you create. Open your file and add the header:

/*
---
name: MyFirstClass
description: The description is not required
requires: [Core/Element, More/URI, /MySecondClass]
provides: [MyFirstClass]
...
*/

var MyFirstClass = new Class({
	// Class constructor
	initialize : function()
	{

	}
});

Please note how to reference another class from another package. Core/Element will get the Element Class within the Core package. You can also reference your own package using yourLibrary/MySecondClass or /MySecondClass

Build your Framework

Everything is now setup for you to use packager. You have two choices, either use the command line or create a php file that will handle everything for you.

Using command line (Does not work on Windows)

  1. Register packages:
    $ packager/packager register mootools-core
    the packager Core has been registered
    
    $ packager/packager register mootools-more
    the packager More has been registered
    
    $ packager/packager register yourLibrary
    the packager yourLibrary has been registered
    
  2. Build yourlibrary.js:
    //  Includes all Classes from Core, More and yourLibrary
    $ packager/packager build Core/* More/* yourLibrary/* > yourlibrary.js
    
    // Includes only few Classes and their dependencies
    $ packager/packager build Core/Core More/Fx.Accordion yourLibrary/MyFirstClass > yourlibrary.js
    

You will find more information about how to use the command line in the packager repository.

Using php

Create a build.php file in the root of your framework and insert the following lines inside.

<?php
// include the packager library
require dirname(__FILE__) . '/packager/packager.php';

// List of Package needed
$packages = array('mootools-core', 'mootools-more', 'yourLibrary');

// Register all the packages needed to create your Framework
$pkg = new Packager($packages);

// List of Component needed for Core. These are just examples, you might need more
$core = array('Core/Core', 'Core/Array', 'Core/String');

// List of Component needed for More. These are just examples, you might need more
$more = array('More/Fx.Accordion', 'More/URI', 'More/Fx.Scroll');

// List of Component needed for More
$yourlibrary = array('yourLibrary/MyFirstClass', 'yourLibrary/MySecondClass');

// List of all Component needed
$components = array_merge($core, $more, $yourlibrary);

// Create the yourlibrary.js file
$pkg->write_from_components('yourlibrary.js', $components);
?>

Sources

Create a JavaScript Framework using Mootools – Part 3: Create your first Class

Create your first Class

Let’s create our first Class named MyFirstClass. For this example I will reuse a code from a previous post: Mootools Tutorial: Create a Class.

var MyFirstClass = new Class({
	// Class constructor
	initialize : function()
	{

	}
});

Framework tree so far:

YourFramework/
–|-mootools-core/
–|-mootools-more/
–|-yourLibrary/
—-|- Source/
——|- MyFirstClass.js
—-|- package.yml (explained step 4)

Create a JavaScript Framework using Mootools – Part 2: Create your Package

Create your Package

Now it is time for you to create your own package.

  1. Create your library folder (yourLibrary/)
  2. Create a “Source” folder inside
  3. Create an empty package.yml file (explained Part 4)

Framework tree so far:

YourFramework/
–|-mootools-core/
–|-mootools-more/
–|-yourLibrary/
—-|- Source/
—-|- package.yml (explained Part 4)

Create a JavaScript Framework using Mootools – Part 1: Clone Mootools Repositories

I always wanted to know how to create a JavaScript framework based on Mootools. I decided to create one during an internal contest in my company to show my peers how simple and how valuable it could be to our web internal projects.

I decided to divide this article into 5 parts:

Clone Mootools Repositories

Mootools Core Classes

Core contains useful classes such as Element(Control the DOM), Request(Ajax requests) or Fx(for special effects). The first thing you will need to do is create a folder for you framework (YourFramework/), get inside and clone Core using git command line:

cd YourFramework/
git clone https://github.com/mootools/mootools-core.git

Framework tree so far:

YourFramework/
–|-mootools-core/
—-|- Source/
—-|- package.yml (explained Part 4)

Mootools More Classes

More is more UI based and contains classes such as Accordion, Tip, URI or Spinner. Same thing as for Core, you will need to clone using git command line:

git clone https://github.com/mootools/mootools-more.git

Framework tree so far:

YourFramework/
–|-mootools-core/
–|-mootools-more/
—-|- Source/
—-|- package.yml (explained Part 4)

Mootools Tutorial: Create a Class

Step one: Create your HTML/CSS page.

You page must contain a place to instantiate your new class. It happens line 5.

<html>
<head>
<script type="text/javascript">
	window.addEvent('domready', function() {
		var myClass = new MyClass();
	});
</script>
<style type="text/css">
.odd, .even {
	display: block;
	height: 100px;
	width: 100px;
	float:left;
}
</style>
</head>
<body>
	<div id="container">
		<div class="odd"></div>
		<div class="even"></div>
		<div class="odd"></div>
		<div class="even"></div>
	</div>
</body>
</html>

Step two: Create your Mootools Class.

All you need is the initilize function (or constructor).

var MyClass = new Class({
	// Class constructor
	initialize : function()
	{

	}
});

Step three: Let’s add some colors.

In this step we will create a setElementsColor function. The purpose of it is just to give colors to elements in order to be able to see them.

var MyClass = new Class({
	// Class constructor
	initialize : function()
	{
		// Set the element colors
		this.setElementsColor();
	},
	
	setElementsColor : function ()
	{
		// Get odd elements and set the background-color
		$$('.odd').setStyle('background-color', 'yellow');

		// Get even elements and set the background-color
		$$('.even').setStyle('background-color', 'orange');
	}
});

Step four: Let’s set Options.

I want to add options to my Class. The proper way is to add an Object as you can see line 4.

<script type="text/javascript">
	window.addEvent('domready', function() {
		var myClass = new MyClass( {
			message:'this is a specific message'
		});
	});
</script>

To be able to use setOptions() function in your class you will need to implement the Mootools Options Class. Line 12 don’t forget to add a parameter that will get Options from the instance creation. Line 15 set the Options and overwrite options line 7,8 and 9.

var MyClass = new Class({

	// Implement Mootools Options Class
	Implements : [Options],
	
	// create options Object
	options : {
		message:'this is a default message'
	},

	// Class constructor
	initialize : function(options)
	{
		// Set the options
		this.setOptions(options);
		// Set the element colors
		this.setElementsColor();
	},
	
	setElementsColor : function ()
	{
		// Get odd elements and set the background-color
		$$('.odd').setStyle('background-color', 'yellow');

		// Get even elements and set the background-color
		$$('.even').setStyle('background-color', 'orange');
	}
});

Step five: Let’s Play with Options.

Now that options are setup, I want to use them. In this step we will create a function that returns the option “message”

var MyClass = new Class({

	// Implement Mootools Options Class
	Implements : [Options],
	
	// create options Object
	options : {
		message:'this is a default message'
	},

	// Class constructor
	initialize : function(options)
	{
		// Set the options
		this.setOptions(options);
		// Set the element colors
		this.setElementsColor();
	},
	
	setElementsColor : function ()
	{
		// Get odd elements and set the background-color
		$$('.odd').setStyle('background-color', 'yellow');

		// Get even elements and set the background-color
		$$('.even').setStyle('background-color', 'orange');
	},
	
	getMessage : function ()
	{
		// return the message
		return this.options.message;
	}
});

Now let’s use this new function.

<script type="text/javascript">
	window.addEvent('domready', function() {
		var myClass = new MyClass( {
			message:'this is a specific message'
		});
	});
	// Result: 'this is a specific message'
	alert(myClass.getMessage());
</script>

Download

mootools_create_class.zip

Sources