
/*
---
description: a mootools 1.2 class for doing nested in-page scrolling navigation

author: Samuel J. Thurston

copyright: 2009 Samuel Thurston

license: MIT-style

homepage: http://samthurston.com/rubik/

requires: core:1.2.4: '*'

provides: [Rubik] 

*/

var Rubik = new Class({
	Implements: [Options,Events],
	options: {
		mode: 'horizontal', // or vertical
		autoMenu: false, //  automatically assign events to this ul/li set.
		menuMap: false, // map how the events are assigned
		nextBtn: false, // assigns a next event with wraparound
		transition: Fx.Transitions.Sine.easeInOut, //  slide transition
		duration: 1000, // slide duration
		current: 0, //  default slide to start on
		scrollAct: true, // automatically assigns mouse-wheel events
		constVelo: true,  // maintain a constant time between multiple slides.
		//class names
		clsRoot: 'rubik_',
		clsCont: 'container',
		clsDraw: 'drawer',
		clsPage: 'page',
		clsFix: '', // use this to differentiate between instances 
		// events
		onScrollStart: function(){},
		onScrollStop: function(){}
	},
	current: 0,
	initialize: function(container,options){
		this.setOptions(options);
		this.current = this.options.current;
		this.container = $(container);
		
		if (this.options.clsFix == '' && this.options.mode == 'horizontal')
		{
			this.options.clsFix = '_h';
		}
		this.drawer = new Element('div',{'class':this.options.clsRoot+this.options.clsDraw+this.options.clsFix});//.set('html',this.container.get('html'));
		this.container.getChildren().each(function(el,idx,ar){
			el.addClass(this.options.clsRoot+this.options.clsPage+this.options.clsFix);
			this.drawer.wraps(el);
		},this);
		
		this.container.addClass(this.options.clsRoot+this.options.clsCont+this.options.clsFix);
		
		if(this.options.mode=='vertical'){
			this.drawer.setStyle('height',this.container.getCoordinates().height*this.container.getChildren('div').length);
		}else{
			this.drawer.setStyle('width',this.container.getCoordinates().width*this.container.getChildren('div').length);
		}
		
		var obj = this;
		// add menu events if specified
		
		if (this.options.autoMenu){
			this.menu = $(this.options.autoMenu);
			this.addMenu(this.menu,this.options.menuMap);
		}
		// add next button event if specified
		if (this.options.nextBtn){
			$(this.options.nextBtn).addEvent('click',function(ev){
					ev.preventDefault();
					obj.next();
			});
		}
		// add scroll events if enabled
		if (this.options.scrollAct){
			this.container.addEvent('mousewheel',function(mwe){
				mwe.stop();
				if(mwe.wheel < 0){
					if (obj.current < obj.drawer.getChildren('div').length-1){
						obj.jumpTo(obj.current+1);
					}
				}else{
					if (obj.current > 0){
						obj.jumpTo(obj.current-1);
					}
				}
			});
		}
		
		// fix for "click bug." 
		// The first call to skipTo() was failing
		// to engage the transition in IE browsers.
		if (Browser.Engine.trident){
			this.drawer.setStyle('zoom','1');
		}
		
		// v1.3 caching fx object
		var time = this.options.duration;
		this.efx = new Fx.Morph(this.drawer,{
			transition: this.options.transition,
			duration: time,
			link: 'cancel'
		});
		
		this.skipTrans(this.options.current);
	},
	
	// public but used internally to transition to a specific slide index.
	jumpTo: function(idx){

		this.fireEvent('scrollStart',[this.current,idx]);
		this.moving = true;
		var time;
		if (this.options.constVelo){
			time = this.options.duration;
		}else{
			time = this.options.duration * Math.abs(this.current - idx);
		}
		
		this.efx.set({duration:time});
		
		var obj = this;
		this.efx.start(this.movement(idx)).chain(function(){
			obj.current = idx;
			obj.moving = false;
			obj.fireEvent('scrollStop',[obj.current,idx]);
		});
	},
	
	// focuses on a specific slide index bypassing the transition,. 
	skipTrans: function(idx){
		this.drawer.setStyles(this.movement(idx));
	},
	
	// private.  Calculates the positioning properties for the transition to the target slide.
	movement: function(idx){
		var movement;
		if(this.options.mode == 'vertical'){
			movement = {top: -(this.drawer.getChildren('div')[idx].getPosition(this.drawer).y)+'px'};
		}else{
			movement = {left: -(this.drawer.getChildren('div')[idx].getPosition(this.drawer).x)+'px'};
		}
		return movement;
	},
	
	// public. Skips to next slide.  If already at the last, rolls over to the beginning.
	next: function(){
		var next = this.current + 1;
		if (next > this.drawer.getChildren().length-1){
			next = 0;
		}

		this.jumpTo(next);
	},
	
	// immediately jumps to the first slide when instantiated without transition.
	reset: function(){
		this.skipTrans(this.options.current);
	},
	
	// used to add the click events to a ul full of links.  map is optional.
	addMenu: function(menuid, map){
		var obj = this;
		if (map){
			// if there's a menuMap specified, add the events to the menu as specified
			$(menuid).getElements('a').each(function(el,idx,ar){
				if (map[idx] !== -1){
					el.addEvent('click',function(ev){
						ev.preventDefault();
						obj.jumpTo(map[idx]);
					});
				}
			});
		}else{
			// otherwise just assign them sequentially.
			$(menuid).getElements('a').each(function(el,idx,ar){
				el.addEvent('click',function(ev){
					ev.preventDefault();
					obj.jumpTo(idx);
				});
			});
		}
	}
});

