Ajax Content for MooTools v1.2 (Facebook Style)

0

Posted by Rob | Posted in Code, Javascript, MooTools | Posted on 22-10-2009

Tags: , , , , ,

Welcome to my second attempt at writing up a mootools class. This one’s a little more advanced this time.

It attaches an Ajax handler to all links on a page which conform to the selector you give it. For example, you can tell it to attach to all standard links (<A HREF=’/here/’>Hello</A>), or links with a specific class as i’ve done with the code below (<A HREF=’/here/’ CLASS=’ajax’>Hello</A>).

I hope someone finds this useful. As ever this was created after I kept saying that I could write something a hell of a lot better than anything else I was finding. This might be a bold statement, but I was having trouble finding anything that would do the job, and simply.


(Demo to come, I’m just posting the code up right now.)

/*
 * ContentHandler
 *
 * Content handler that attaches to links and loads content through AJAX
 *
 * @author Rob Taylor
 * @created 28/09/09
 * @modified 29/09/09
 * @version 1.3.5.10
 */

	var ContentHandler = new Class({
		Implements: [Options, Events, Log],

		options : {
			// Array of container elements to load via AJAX
			container		: $('content'),

			// Loading graphic and text to display while working
			loadingImage		: '/images/ajax-loader.gif',
			loadingMessage		: 'Loading Content...',

			// How much Opacity to set on 'Container' while loading new page.
			loadingTransparency	: 0,

			// Type of link to load via AJAX. Defaults to <a> tags
			linkIdentifier		: 'a',

			// TRUE to enable console logging. FALSE by default
			debugMode		: false
		},

		currentURL : '',
		isNavigating : false,

		/*
		 * initialize
		 *
		 * Default constructor. Sets configuration options, attaches load event links and triggers initial load
		 *
		 * @param mixed		options		Array of config options.  Supported elements:
		 *					container		-	Container element to load Ajax into
		 *					loadingImage		-	URL of image to display while working
		 *					loadingMessage		-	Message to display while working
		 *					loadingTransparency	-	Transparency level of 'container' when request is made.  Must be between 0 and 1. Defaults to 0
		 *					linkIdentifier		-	HTML tagname to attach onclick loader to. Defaults to <a>
		 *					debugMode		-	TRUE to enable console logging. FALSE by default
		 */
		initialize : function( options ) {

			// Set configuration options
			this.setOptions(options);

			this.debug('Starting :: ContentHandler');

			// Loop through link elements attaching loader
			$$(this.options.linkIdentifier).each( function( element ){
				this.attachHandler( element );
			}.bind(this));

			// Do initial content load if location is specified
			if( window.location.hash && window.location.hash.length > 1 )
			{
				// Get initial page
				var url = window.location.hash.substr( 1, window.location.hash.length );

				this.getContent( url, true );
			}

			// Attach check to re-load content if URL changes or previous attempt fails
			this.updateContent.periodical( 200, this );

		},

		/*
		 * createLoader
		 *
		 * Populates and displays
		 */
		createLoader : function(){
			this.debug('Creating :: Loader Image');

			// Container padding
			var padding		= 40;

			// Create image element
			var content		= $( this.options.container );
			var loaderImage		= new Element( 'img', {
				src	: this.options.loadingImage,
				alt	: 'Loading',
				title	: 'Loading'
			});

			// Create test element
			var loaderMessage	= new Element( 'div' ).set('text',this.options.loadingMessage);
			loader			= new Element( 'div' );

			// Style elements
			content.setStyle( 'position', 'relative' );
			loader.setStyles({
				margin		: 'auto',
				textAlign	: 'center',
				position	: 'absolute',
				opacity		: 0
			});

			// Autoresize container onload to fit image
			loaderImage.addEvent('load', function(){
				loader.setStyles({
					height		: ( loaderImage.getSize().y + padding ),
					width		: ( loaderImage.getSize().x + padding ),
					top		: 20 + content.getPosition().y,
					left		: ( ( ( content.getSize().x / 2 ) - ( loaderImage.getSize().x / 2 ) ) - padding ) + content.getPosition().x
				});
			});
			// Add image and message to container
			loader.adopt( loaderImage,loaderMessage );

			// Attach loader to content area and fade in
			$(document.body).adopt( loader );
			loader.fade('in');

		},

		destroyLoader : function(){
			if( loader )
				loader.destroy();
		},

		getContent : function( url, freshStart ){
			this.debug('Collecting :: Page Content');

			var content = $( this.options.container );

			if( this.isNavigating && request_content )
				request_content.cancel();

			request_content = new Request.HTML({
				url		: url,
				method		: 'get',
				link		: 'cancel',
				update		: content,
				onRequest	: function(){
					this.debug('Collecting :: Page Content -> Request Made');

					// Set active request flag (prevents periodical spawning simultaneous requests)
					this.isNavigating = true;

					// Store current content in case of rollback (e.g. if request fails)
					content.store( 'content', content.get('html') );

					if( freshStart ){
						// Flush content
						content.empty();
					}
					else{
						content.get('tween', {
							property: 'opacity',
							duration: 'normal'
						}).start( this.options.loadingTransparency );
					}

					// Display loaders
					this.createLoader();

				}.bind(this),
				onComplete	: function(){
					this.debug('Collecting :: Page Content -> Complete');

					// Reset active request flag
					this.isNavigating = false;

					this.destroyLoader();

					if( !freshStart ){
						content.get('tween', {
							property: 'opacity',
							duration: 'short'
						}).start(1);
					}

				}.bind(this),
				onSuccess	: function(){
					this.debug('Collecting :: Page Content -> Success');

					// Track new content URL
					this.currentURL = url;

					// Flush stored content
					content.store( 'content', '' );

					if( !freshStart ){
						window.location.hash = url;
					}

					content.getElements( this.options.linkIdentifier ).each( function( inner_element ){
						this.attachHandler( inner_element );
					}.bind(this));

					this.debug('Current URL: '+this.currentURL);

				}.bind(this),
				onFailure	: function(){
					this.debug('Collecting :: Page Content -> Fail');

					// Retrieve stored content
					content.retrieve( 'content' );

				}
			}).send();

			return false;

		},

		attachHandler : function( element ){
			if( $(element) ){
				if( this.checkLink( $(element).get('href') ) )
				{
					url = $(element).get('href');
					//this.debug('Adding Handler : "'+url+'"');
					$(element).addEvent('click', this.getContent.bind(this,url) );
				}
			}
			else{
				this.debug('No Valid Element.');
			}
		},

		updateContent : function(){

			var url = window.location.hash.substr( 1, window.location.hash.length );
			if( this.currentURL != url && !this.isNavigating && this.checkLink( url ) ){
				this.debug('Updating :: Page Content: "'+url+'"');
				this.getContent( url );
			}

		},

		checkLink : function( url ){

			if( url.test('^http:|^https:' ) ) // We're assuming here, that external links, do not want to be loaded inside the container.
				return false;

			return true;
		},

		debug : function( message ){

			if( !this.options.debugMode )
				return;

			this.log( '[Content Handler] ' + message );

		}

	});

	document.addEvent('domready', function(){
		content = new ContentHandler({
			linkIdentifier : 'a.ajax' // this will attach to any link with a class of 'ajax'.
			//linkIdentifier : 'a' // This will attach to any link.
		});
	});

Available Options

  • container : Container element to load Ajax into
  • loadingImage : URL of image to display while working
  • loadingMessage : Message to display while working
  • loadingTransparency : Transparency level of ‘container’ when request is made. Must be between 0 and 1. Defaults to 0
  • linkIdentifier : HTML tagname to attach onclick loader to. Defaults to <a>
  • debugMode : TRUE to enable console logging. FALSE by default

Write a comment