MooTools Class Creation Tips II

By  on  

A while back I shared a few MooTools class creation tips that I live by. More experience in creating MooTools plugins has given me some new guidelines to share.

Code "Inline" First, Then Class-ize; Don't Force It

The number one tip I give to MooTools Class rookies is this: create the desired class functionality before turning it into a class. Once you have the code working using "inline" MooTools JavaScript, evaluate what you have. You may decide that your functionality is too specific to the task at hand that making a Class isn't the right choice.

If you do think you should turn the code into a class, take a look at all of your initial "var {key} = {value}" declarations -- those will likely be your arguments and options. Take any repeated code and turn them into class methods. Do the same for specific functionalities the Class should have. Here's a very basic example:

window.addEvent('domready',function() {
	/* settings */
	var showDuration = 3000;
	var container = $('slideshow-container');
	var images = container.getElements('img');
	var currentIndex = 0;
	var interval;
	var toc = [];
	var tocWidth = 20;
	var tocActive = 'toc-active';
	
	/* new: starts the show */
	var start = function() { interval = show.periodical(showDuration); };
	var stop = function() { $clear(interval); };
	/* worker */
	var show = function(to) {
		images[currentIndex].fade('out');
		toc[currentIndex].removeClass(tocActive);
		images[currentIndex = ($defined(to) ? to : (currentIndex < images.length - 1 ? currentIndex+1 : 0))].fade('in');
		toc[currentIndex].addClass(tocActive);
	};
	
	/* new: control: table of contents */
	images.each(function(img,i){
		toc.push(new Element('a',{
			text: i+1,
			href: '#',
			'class': 'toc' + (i == 0 ? ' ' + tocActive : ''),
			events: {
				click: function(e) {
					if(e) e.stop();
					stop();
					show(i);
				}
			},
			styles: {
				left: ((i + 1) * (tocWidth + 10))
			}
		}).inject(container));
		if(i > 0) { img.set('opacity',0); }
	});
	
	/* new: control: next and previous */
	var next = new Element('a',{
		href: '#',
		id: 'next',
		text: '>>',
		events: {
			click: function(e) {
				if(e) e.stop();
				stop(); show();
			}
		}
	}).inject(container);
	var previous = new Element('a',{
		href: '#',
		id: 'previous',
		text: '<<',
		events: {
			click: function(e) {
				if(e) e.stop();
				stop(); show(currentIndex != 0 ? currentIndex -1 : images.length-1);
			}
		}
	}).inject(container);
	
	/* new: control: start/stop on mouseover/mouseout */
	container.addEvents({
		mouseenter: function() { stop(); },
		mouseleave: function() { start(); }
	});
	
	/* start once the page is finished loading */
	window.addEvent('load',function(){
		start();
	});
});

And now the Class version:

var SimpleSlideshow = new Class({
	options: {
		showControls: true,
		showDuration: 3000,
		showTOC: true,
		tocWidth: 20,
		tocClass: 'toc',
		tocActiveClass: 'toc-active'
	},
	Implements: [Options,Events],
	initialize: function(container,elements,options) {
		//settings
		this.container = $(container);
		this.elements = $$(elements);
		this.currentIndex = 0;
		this.interval = '';
		if(this.options.showTOC) this.toc = [];
		
		//assign
		this.elements.each(function(el,i){
			if(this.options.showTOC) {
				this.toc.push(new Element('a',{
					text: i+1,
					href: '#',
					'class': this.options.tocClass + '' + (i == 0 ? ' ' + this.options.tocActiveClass : ''),
					events: {
						click: function(e) {
							if(e) e.stop();
							this.stop();
							this.show(i);
						}.bind(this)
					},
					styles: {
						left: ((i + 1) * (this.options.tocWidth + 10))
					}
				}).inject(this.container));
			}
			if(i > 0) el.set('opacity',0);
		},this);
		
		//next,previous links
		if(this.options.showControls) {
			this.createControls();
			
		}
		//events
		this.container.addEvents({
			mouseenter: function() { this.stop(); }.bind(this),
			mouseleave: function() { this.start(); }.bind(this)
		});

	},
	show: function(to) {
		this.elements[this.currentIndex].fade('out');
		if(this.options.showTOC) this.toc[this.currentIndex].removeClass(this.options.tocActiveClass);
		this.elements[this.currentIndex = ($defined(to) ? to : (this.currentIndex < this.elements.length - 1 ? this.currentIndex+1 : 0))].fade('in');
		if(this.options.showTOC) this.toc[this.currentIndex].addClass(this.options.tocActiveClass);
	},
	start: function() {
		this.interval = this.show.bind(this).periodical(this.options.showDuration);
	},
	stop: function() {
		$clear(this.interval);
	},
	//"private"
	createControls: function() {
		var next = new Element('a',{
			href: '#',
			id: 'next',
			text: '>>',
			events: {
				click: function(e) {
					if(e) e.stop();
					this.stop(); 
					this.show();
				}.bind(this)
			}
		}).inject(this.container);
		var previous = new Element('a',{
			href: '#',
			id: 'previous',
			text: '<<',
			events: {
				click: function(e) {
					if(e) e.stop();
					this.stop(); 
					this.show(this.currentIndex != 0 ? this.currentIndex -1 : this.elements.length-1);
				}.bind(this)
			}
		}).inject(this.container);
	}
});

See how easy the transition is when you have the inline code in front of you? Simple!

Add Events When Possible

Adding events to your classes gives a completely new level of control over the class functionality. Consider my Overlay class. I spent 95% of the time creating the plugin functionality and 5% implementing events but I consider events essential to the Overlay. Here's a quick example of implementing events:

//.....
Implements: [Options,Events],
open: function() {
	this.fireEvent('open');
	//do stuff
},
//.....

Now my Overlay class allows you fire events at 4 different times. More flexible and a perfect usage of events.

Evaluate Options vs. Arguments

Judge your list of options and your class' primary responsibilities to determine if all of the options should be kept as options and not as arguments, and visa versa. When I come to a 50/50 decision I choose to make the parameter an option. If the class cannot live without the given option/argument and there's no suitable default, keep it as an argument.

Chaining: return this;

One of the great advantages of using a JavaScript frameworks and classes is the ability to chain method class to make your code shorter. And implementing that type of functionality is so easy!

//.....
store: function(key,value) {
	this.data[key] = value;
	return this;
}
//.....

Don't let your methods end with nothing -- return this!

Put it in the Forge!

Share your awesome plugin by posting it in the MooTools Forge. Sharing your plugins in the Forge allows you to easily get feedback, improvements, and requests.

There you have it. Feel free to share any of your tips!

Recent Features

  • By
    Chris Coyier&#8217;s Favorite CodePen Demos

    David asked me if I'd be up for a guest post picking out some of my favorite Pens from CodePen. A daunting task! There are so many! I managed to pick a few though that have blown me away over the past few months. If you...

  • By
    Vibration API

    Many of the new APIs provided to us by browser vendors are more targeted toward the mobile user than the desktop user.  One of those simple APIs the Vibration API.  The Vibration API allows developers to direct the device, using JavaScript, to vibrate in...

Incredible Demos

Discussion

  1. I did exactly this when I made a class to allow of tab-indent in a textarea. I have it up on github, but I can not for the life of me add a tag to it.

    The forge requires this, so for the foreseeable future, the forge will not get it.

    http://github.com/ameyer/MooIndent

  2. @adam –

    $ git commit -m ‘the best commit ever’
    $ git tag 1.0
    $ git push –tags origin master

    @ David

    I find working with code in a class from the start to be much easier to look at and also to debug, but for somebody new I ‘spose it’s good advice.

    As for arguments v. options I think you nailed it.

    Great article. I hope that everybody who dabbles in mootools will one day figure how to use `Class`. It’s incredibly awesome.

  3. @adam – The comments removed one of the -‘s from my post.

    $ git push – – tags origin master

    with two -, no space.

  4. @Ryan Florence:

    Thanks! Sadly, even though I see now 2 tags in github, the forge does not.

    “GitHub repository has no tags. At least one tag is required.”

    The forge just hates me. What can I say?

  5. @Adam Meyer: Feel free to jump on #davidwalshblog chat on Freenode IRC if you want more help!

  6. @Adam @Ryan

    The last command is:

    git push –-tags origin master

    with two ‘-‘ before the ‘tags’ argument.

  7. Great article David.
    Personally I am still struggling with Mootools classes and definitely consider myself to be a MooTools Class (at best).
    Your advice here, ie to create functions then convert it into a Class later sounds like a good idea, I will give it a go for my next Class :)

    Chris

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!