/*!
 * jLim Core
 *
 * jLim is a small JavaScript base code. It can be used to built your own
 * JavaScript library or framework.
 *
 * @version   0.1.2
 * @author    Victor Villaverde Laan
 * @link      http://www.freelancephp.net/jlim-small-javascript-framework/
 * @license   MIT license
 */
(function ( window ) {

/**
 * jLim function
 * @param {string|DOM node|array of DOM nodes} selector  When selector is not a string the context will be ignored
 * @param {string|DOM node|array of DOM nodes} context
 * @return {jLim} New instance
 */
var jLim = window.jLim = function ( selector, context ) {
	return new jLim.core.init( selector, context );
};

// set $ global ( when not already exists )
if ( ! window.$ )
	window.$ = jLim;

/**
 * jLim Core
 */
jLim.core = jLim.prototype = {

	els: [],
	selector: [],
	context: [],

	/**
	 * Init function, set selected elements
	 * @param {string|DOM node|array of DOM nodes} selector  When selector is not a string the context will be ignored
	 * @param {string|DOM node|array of DOM nodes} context  Optional, default is document
	 */
	init: function ( selector, context ) {
		// set selector and context property
		this.selector = selector || document;
		this.context = context || document;

		// get elements by selector
		if ( typeof selector == 'string' ) {
			// trim spaces at the begin and end
			selector = jLim.trim( selector );

			if ( selector == 'body' ) {
				// set body
				this.els = document.body;
			} else if ( selector.substr( 0, 1 ) == '<' ) {
				// create element
				this.els = jLim.create( selector );
			} else {
				// find elements
				this.els = jLim.selector( selector, context );
			}
		} else if ( jLim.isFunction( selector ) ) {
			// set DOM ready function
			jLim.ready( selector );
		} else if ( selector instanceof jLim ) {
			this.els = selector.get();
		} else {
			this.els = selector;
		}

		// make sure elements property is an array
		if ( ! jLim.isArray( this.els ) )
			this.els = this.els ? [ this.els ] : [];

		// support for using jLim object as an array
		// set indices
		for ( var i in this.els )
			this[ i ] = this.els[ i ];

		// set length property
		this.length = this.els.length;
	},

	/**
	 * Get element (return all current elements when no index given)
	 * @param {integer} index
	 * @return {DOM node|array of DOM nodes}
	 */
	get: function ( index ) {
		if ( typeof index == 'undefined' )
			return this.els;

		var el = ( index === -1 )
				? this.els.slice( index )
				: this.els.slice( index, +index + 1 );

		return el.length ? el[0] : null;
	},

	/**
	 * Get count of current elements
	 * @return {integer}
	 */
	size: function () {
		return this.els.length;
	},

	/**
	 * Call function for each element
	 * @param {function} fn
	 * @param {array} args
	 * @return {this} For chaining
	 */
	each: function ( fn, args ) {
		jLim.each( this.els, fn, args );
		return this;
	},

	/**
	 * Find elements within the current elements (context)
	 * @param {string} selector
	 * @return {jLim} Instance of new selection
	 */
	find: function ( selector ) {
		return this.chain( selector, this.els );
	},

	/**
	 * Add to the current elements in a new created jLim object
	 * @param {string|DOM node|array of DOM nodes} selector  When selector is not a string the context will be ignored
	 * @param {string|DOM node|array of DOM nodes} context  Optional, when none is given it will use the context of current jLim object
	 * @return {jLim} Instance of new selection
	 */
	add: function ( selector, context ) {
		var $new = this.chain( selector, context || this.context ),
			els = this.els.concat( $new.get() );

		$new.els = els;
		return $new;
	},

	/**
	 * Set one of current elements as new jLim object
	 * @param {integer} index  Negative integer also possible, -1 means last item
	 * @return {jLim} Instance of new selection
	 */
	eq: function ( index ) {
		return this.chain( this.get( index ) );
	},

	/**
	 * Set slice of current elements as new jLim object
	 * @param {integer} start  Like the first param of the standard Array.slice() function
	 * @param {integer} end  Like the second param of the standard Array.slice() function
	 * @return {jLim} Instance of new selection
	 */
	slice: function ( start, end ) {
		var els = this.els.slice( start, end || this.els.length );
		return this.chain( els );
	},

	/**
	 * Chain content as new jLim object
	 * @param {string|DOM node|array of DOM nodes} selector  When selector is not a string the context will be ignored
	 * @param {string|DOM node|array of DOM nodes} context  Optional
	 * @return {jLim} Instance of new selection
	 */
	$: function ( selector, context ) {
		var $new = jLim( selector, context );
		$new.prevjLim = this;
		return $new;
	},

	/**
	 * Set pointer to previous jLim object
	 * @return {jLim} Previous jLim object in the chain
	 */
	end: function () {
		return this.prevjLim || jLim( null );
	}

};

// deprecated chain() method replaced by $
jLim.core.chain = jLim.core.$;

// init function gets core prototype
jLim.core.init.prototype = jLim.core;

/**
 * Extend method
 * @return {jLim|jLim.core|object|array|boolean}
 */
jLim.extend = jLim.core.extend = function () {
	// target is current object if only one argument
	var i = 0,
		target = this,
		deep = false,
		obj, empty, item, x;

	// check extending recursive (deep)
	if ( typeof arguments[ 0 ] === 'boolean' ) {
		deep = true;
		i = 1;

		if ( arguments.length > 2 ) {
			i = 2;
			target = arguments[ 1 ];
		}
	} else if ( arguments.length > 1 ) {
		i = 1;
		target = arguments[ 0 ];
	}

	// loop through all source objects
	for ( x = i; x < arguments.length; x++ ) {
		obj = arguments[ x ];

		// copy object items (properties and methods)
		for ( item in obj ) {
			if ( obj[ item ] === target )
				continue;

			if ( deep && typeof obj[ item ] == 'object' && obj[ item ] !== null ) {
				// item is also object, make copy
				empty = jLim.isArray( obj[ item ] ) ? [] : {};
				target[ item ] = jLim.extend( deep, target[ item ] || empty, obj[ item ] );
			} else {
				// copy property or method
				target[ item ] = obj[ item ];
			}
		}
	}

	// return modified target
	return target;
};

/**
 * Extent basic functions
 */
jLim.extend({

	/**
	 * Selector method
	 * @param {string} selector
	 * @param {string|DOM node|array of DOM nodes} context  Default document
	 * @return {DOM node|array of DOM nodes|empty array}
	 */
	selector: function ( selector, context ) {
		return jLim.$$( selector, context );
	},

	/**
	 * Add DOMReady callbacks
	 * @param {function|string} fn  When string will be run like code with eval()
	 * @return {this}
	 */
	ready: function ( fn ) {
		// default use the DOMReady add() method
		jLim.DOMReady.add( fn );

		// return this for chaining
		return this;
	},

	/**
	 * Create DOM element
	 * @param {string} html
	 * @return {DOM element|array of DOM elements}
	 */
	create: function ( html ) {
		var ph = document.createElement( 'div' ),
			els = [];

		ph.innerHTML = html;

		// get created elements
		els = ph.childNodes;

		// return element or array of elements
		return els.length == 1 ? els[0] : els;
	},

	/**
	 * Each function for arrays and objects
	 * @param {object|array} obj
	 * @param {function} fn
	 * @return {this}
	 */
	each: function ( obj, fn ) {
		var item, retVal;

		// call given function for each item
		for ( item in obj ) {
			retVal = fn.call( obj[ item ], item, obj[ item ] );

			// do not continue further when return value is false
			if ( retVal === false )
				break;
		}

		// return this for chaining
		return this;
	},

	/**
	 * Trim spaces
	 * @param {string} str
	 * @return {string}
	 */
	trim: function ( str ) {
		return str.replace( /^\s+/, '' ).replace( /\s+$/, '' );
	},

	/**
	 * Check if argument is array
	 * @param {mixed} obj
	 * @return {boolean}
	 */
	isArray: function ( obj ) {
		return ( Object.prototype.toString.call( obj ) === "[object Array]" );
	},

	/**
	 * Check if argument is function
	 * @param {mixed} obj
	 * @return {boolean}
	 */
	isFunction: function ( obj ) {
		return ( Object.prototype.toString.call( obj ) === "[object Function]" );
	}

});

/**
 * External components
 */

/**
 * DOMReady
 * 
 * @version   1.0
 * @link      http://www.freelancephp.net/domready-javascript-object-cross-browser/
 * @license   MIT license
 */
jLim.DOMReady=(function(){var fns=[],isReady=false,errorHandler=null,getFunc=function(fn){if(typeof fn=='string')return function(){eval(fn);};return fn;},ready=function(){isReady=true;for(var x=0;x<fns.length;x++){try{fns[x]();}catch(err){if(errorHandler)errorHandler(err);}}};this.setOnError=function(fn){errorHandler=getFunc(fn);return this;};this.add=function(fn){fn=getFunc(fn);if(isReady){fn();}else{fns[fns.length]=fn;}return this;};if(window.addEventListener){document.addEventListener('DOMContentLoaded',function(){ready();},false);}else{(function(){if(!document.uniqueID&&document.expando)return;var tempNode=document.createElement('document:ready');try{tempNode.doScroll('left');ready();}catch(err){setTimeout(arguments.callee,0);}})();}return this;})();

/**
 * SS
 *
 * @version   0.1.1
 * @link      http://www.freelancephp.net/simple-css-selector-function/
 * @license   MIT license
 */
var $$=function(selector,context){var isObjType=function(o,type){return Object.prototype.toString.call(o)==='[object '+type+']';},isDesc=function(d,a){return d.parentNode==a||d.tagName.toUpperCase()!='HTML'&&isDesc(d.parentNode,a);},s=selector,c=context,els=[];s=s.split(',');c=isObjType(c,'String')?$$(c):c&&c.length?c:document;if(!isObjType(c,'Array'))c=[c];for(var i in c){for(var j in s){var strim=s[j].replace(/\s+/g,''),sp=s[j].split(' '),op=strim.substr(0,1),name=strim.substr(1),tels=[],nextContext;if(sp.length>1){nextContext=$$(sp[0],c[i]);tels=$$(sp.slice(1).join(' '),nextContext);els=els.concat(tels);}else if(op=='#'){tels[0]=c[i].getElementById?c[i].getElementById(name):document.getElementById(name);if(tels[0]&&isDesc(tels[0],c[i]))els.push(tels[0]);}else if(op=='.'){var expr=new RegExp('(^|\\s)'+name+'(\\s|$)'),all=c[i].getElementsByTagName('*');for(var v=0,w=all.length;v<w;v++){if(expr.test(all[v].className))els.push(all[v]);}}else{tels=c[i].getElementsByTagName(strim);for(var y=0,z=tels.length;y<z;y++)els.push(tels[y]);}}}return els.length==1?els[0]:els;};
jLim.$$=$$;

})( window );

/*!
 * jLim DOM Plugin
 *
 * DOM related methods for jLim
 *
 * @version   0.1
 * @author    Victor Villaverde Laan
 * @link      http://www.freelancephp.net/jlim-dom-plugin/
 * @license   MIT license
 */
jLim.core.extend({

	/**
	 * Remove given attribute
	 * @param {string} name
	 * @param {mixed} value Optional, when not given used as getter for first element
	 * @return {this|mixed}
	 */
	attr: function ( name, value ) {
		// getter
		if ( typeof value == 'undefined' ) {
			if ( typeof name == 'string' ) {
				// getter
				return this.length ? this.get(0)[ name ] : null;
			} else {
				// setter multiple attrs
				for ( var key in name ) {
					this.attr( key, name[ key ] );
				}

				return this;
			}
		}

		// setter
		return this.each(function(){
			if ( typeof this[ name ] != 'undefined' ) {
				if ( value === null ) {
					this.removeAttribute( name );
				} else {
					this[ name ] = value;
				}
			}
		});
	},

	/**
	 * Remove given attribute
	 * @param {string} name
	 * @return {this}
	 */
	removeAttr: function ( name ) {
		return this.attr( name, null );
	},

	/**
	 * Set html of selected elements
	 * @param {string} html Optional, when not given used as getter for first element
	 * @return {this|string}
	 */
	html: function ( html ) {
		// getter
		if ( typeof html == 'undefined' )
			return this.length ? this.get(0).innerHTML : null;

		// setter
		try {
			this.each(function(){
				this.innerHTML = html;
			});
		} catch ( e ) {
			this.empty().append( html );
		}

		return this;
	},

	/**
	 * Empty all content of selected elements
	 * @return {this}
	 */
	empty: function () {
		var el;

		for ( var x = 0, l = this.size(); x < l; x++ ) {
			el = this[ x ];

			while ( el.firstChild )
				el.removeChild( el.firstChild );
		}

		return this;
	},

	/**
	 * Set text of selected elements
	 * @param {string} text Optional, when not given used as getter for first element
	 * @return {this|string}
	 */
	text: function ( text ) {
		// getter
		if ( typeof text == 'undefined' )
			return this.length ? this.get(0).innerText : null;

		// setter, first empty element content and then add text
		return this.empty().each(function(){
			var textNode = document.createTextNode( text );
			$( this ).append( textNode );
		});
	},

	/**
	 * Remove selected elements
	 * @return {this}
	 */
	remove: function () {
		return this.each(function(){
			this.parentNode.removeChild( this );
		});
	},

	/**
	 * Append content to selected elements
	 * @param {jLim|string|DOM node|array of DOM nodes} content
	 * @return {this}
	 */
	append: function ( content ) {
		var $content = jLim( content );

		return this.each(function( i ){
			var target = this;

			// clone content when more then one targets
			if ( i > 0 ) $content = $content.clone();

			// add each content element as child of target
			$content.each(function( i ){
				target.appendChild( $content.get( i ) );
			})
		});
	},

	/**
	 * Prepend content to selected elements
	 * @param {jLim|string|DOM node|array of DOM nodes} content
	 * @return {this}
	 */
	prepend: function ( content ) {
		var $content = jLim( content );

		return this.each(function( i ){
			// clone content when more then one targets
			if ( i > 0 ) $content = $content.clone();

			if ( this.childNodes.length > 0 ) {
				$content.insertBefore( this.childNodes[0] );
			} else {
				$( this ).append( $content );
			}
		});
	},

	/**
	 * Append selected elements to given target
	 * @param {jLim|string|DOM node|array of DOM nodes} content
	 * @return {this}
	 */
	appendTo: function ( target ) {
		jLim( target ).append( this );
		return this;
	},

	/**
	 * Prepend selected elements to given target
	 * @param {jLim|string|DOM node|array of DOM nodes} target
	 * @return {this}
	 */
	prependTo: function ( target ) {
		jLim( target ).prepend( this );
		return this;
	},

	/**
	 * Insert selected elements after given target
	 * @param {jLim|string|DOM node|array of DOM nodes} target
	 * @return {this}
	 */
	insertAfter: function ( target ) {
		var $target = jLim( target ),
			self = this;

		$target.each(function( i ){
			// clone self when more then one targets
			var $content = ( i > 0 ) ? self.clone() : self,
				target = this;

			// set other content element
			$content.each(function( i ){
				if ( i == 0 ) {
					// set first element after target
					target.parentNode.insertBefore( this, target.nextSibling );
				} else {
					// set other elements
					var prev = $content.get( i - 1 );
					prev.parentNode.insertBefore( this, prev.nextSibling );
				}
			});
		});

		return this;
	},

	/**
	 * Insert selected elements before given target
	 * @param {jLim|string|DOM node|array of DOM nodes} target
	 * @return {this}
	 */
	insertBefore: function ( target ) {
		var $target = jLim( target ),
			self = this;

		$target.each(function( i ){
			// clone self when more then one targets
			var $content = ( i > 0 ) ? self.clone() : self;

			// set first element before target
			this.parentNode.insertBefore( $content.get( 0 ), this );

			// insert other elements after the first
			$content.slice( 1 ).insertAfter( $content.get( 0 ) );
		});

		return this;
	},

	/**
	 * Insert content after selected elements
	 * @param {jLim|string|DOM node|array of DOM nodes} content
	 * @return {this}
	 */
	after: function ( content ) {
		jLim( content ).insertAfter( this );
		return this;
	},

	/**
	 * Insert content before selected elements
	 * @param {jLim|string|DOM node|array of DOM nodes} content
	 * @return {this}
	 */
	before: function ( content ) {
		jLim( content ).insertBefore( this );
		return this;
	},

	/**
	 * Replace selected elements with the given content
	 * @param {jLim|string|DOM node|array of DOM nodes} content
	 * @return {this}
	 */
	replaceWith: function ( content ) {
		var $content = jLim( content );

		return this.each(function( i ){
			// clone content when more then one targets
			$content = ( i > 0 ) ? $content.clone() : $content;

			// replace with first element
			this.parentNode.replaceChild( $content.get( 0 ), this );

			// insert other elements after the first
			$content.slice( 1 ).insertAfter( $content.get( 0 ) );
		});
	},

	/**
	 * Clone selected elements
	 * @return {jLim} Instance of the clone
	 */
	clone: function () {
		var els = [];

		this.each(function(){
			var clone;

			if ( typeof this.cloneNode != 'undefined' ) {
				// clone DOM node
				clone = this.cloneNode( true );
			} else if ( typeof this == 'object' ) {
				// clone object or array
				clone = jLim.extend( {}, this );
			} else {
				clone = this;
			}

			els.push( clone );
		});

		return this.chain( els );
	}

});

/*!
 * jLim CSS
 *
 * CSS related methods for jLim
 *
 * @version   0.1
 * @author    Victor Villaverde Laan
 * @link      http://www.freelancephp.net/jlim-css-plugin/
 * @license   MIT license
 */
jLim.core.extend({

	/**
	 * Add class to selected elements
	 * @param {string} value
	 * @return {this}
	 */
	addClass: function ( value ) {
		return this.each(function(){
			if ( ! $( this ).hasClass( value ) )
				this.className += ' '+ value;
		});
	},

	/**
	 * Remove class from selected elements
	 * @param {string} value
	 * @return {this}
	 */
	removeClass: function ( value ) {
		return this.each(function(){
			this.className = this.className.replace( new RegExp( '(\\s|^)' + value + '(\\s|$)' ), '' );
		});
	},

	/**
	 * Check if first element has given class
	 * @param {string} value
	 * @return {boolean}
	 */
	hasClass: function ( value ) {
		return ( this.length && this.get(0).className.match( new RegExp( '(\\s|^)' + value + '(\\s|$)' ) ) );
	},

	/**
	 * Set style(s) of the style attribute
	 * @param {string|object} style
	 * @param {mixed} value Optional
	 * @return {this|string}
	 */
	style: function ( style, value ) {
		var key;

		// getter
		if ( typeof value == 'undefined' ) {
			if ( typeof style == 'string' ) {
				// getter
				key = jLim.toCamelCase( style );
				return this.length ? this.get(0).style[ key ] : null;
			} else {
				// setter multiple styles
				for ( key in style )
					this.style( key, style[ key ] );

				return this;
			}
		}

		// setter
		key = jLim.toCamelCase( style );

		return this.each(function(){
			if ( typeof this.style[ key ] != 'undefined' )
				this.style[ key ] = value;
		});
	}

});

jLim.extend({

	/**
	 * Convert dashed names to camel case
	 * F.e. border-bottom-width to borderBottomWidth
	 */
	toCamelCase: function( str ){
		return str.replace( /(\-[a-z])/g, function( $1 ){
			return $1.toUpperCase().replace( '-', '' );
		});
	}

});

/*!
 * jLim Ajax Plugin
 *
 * Based on SimpleAjax (see http://www.freelancephp.net/simpleajax-small-ajax-javascript-object/)
 *
 * @version   0.2
 * @author    Victor Villaverde Laan
 * @link      http://www.freelancephp.net/jlim-ajax-plugin/
 * @license   MIT license
 */
jLim.core.extend({

	/**
	 * Set content loaded by an ajax call
	 * @param {string} url The url of the ajax call ( include GET vars in querystring )
	 * @param {string} data Optional, the POST data, when set method will be set to POST
	 * @param {function} complete Optional, callback when loading is completed
	 * @return {this} For chaining
	 */
	load: function ( url, data, complete ) {
		var self = this;

		jLim.ajax({
			url: url,
			type: data ? 'POST' : 'GET',
			data: data || null,
			complete: complete || null,
			success: function ( html ) {
				self.html( html );
			}
		});

		return this;
	}

});

jLim.extend({

	// Default ajax settings
	ajaxSettings: {
		url: '',
		type: 'GET',
		dataType: 'text', // text, html, json or xml
		async: true,
		cache: true,
		data: null,
		contentType: 'application/x-www-form-urlencoded',
		success: null,
		error: null,
		complete: null,
		accepts: {
			text: 'text/plain',
			html: 'text/html',
			xml: 'application/xml, text/xml',
			json: 'application/json, text/javascript'
		}
	},

	/**
	 * Change the default ajax settings
	 * @param {object} options Overwrite the default settings, see ajaxSettings
	 */
	ajaxSetup: function ( settings ) {
		jLim.extend( jLim.ajaxSettings, settings );
	},

	/**
	 * Ajax call
	 * @param {object} options Optional, overwrite the default settings for this call, see ajaxSettings
	 * @return {XMLHttpRequest|ActiveXObject}
	 */
	ajax: function( options ) {
		var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject( 'Microsoft.XMLHTTP' ),
			opts = jLim.extend( {}, jLim.ajaxSettings, options ),
			ready = function () {
				if( xhr.readyState == 4 ){
					if ( xhr.status == 200 ) {
						// set data
						var data = ( opts.dataType == 'xml' ) ? xhr.responseXML : xhr.responseText;

						// parse json data
						if ( opts.dataType == 'json' )
							data = jLim.parseJSON( data );

						// success callback
						if ( jLim.isFunction( opts.success ) )
							opts.success.call( opts, data, xhr.status, xhr );
					} else {
						// error callback
						if ( jLim.isFunction( opts.error ) )
							opts.error.call( opts, xhr, xhr.status );
					}

					// complete callback
					if ( jLim.isFunction( opts.complete ) )
						opts.complete.call( opts, xhr, xhr.status );
				}
			};

		// prevent browser caching
		if ( ! opts.cache )
			opts.url += (( opts.url.indexOf( '?' ) > -1 ) ? '&' : '?' ) + '_nocache='+ ( new Date() ).getTime();

		// set send data
		if ( opts.data ) {
			if ( opts.type == 'GET' ) {
				opts.url += (( opts.url.indexOf( '?' ) > -1 ) ? '&' : '?' ) + jLim.param( opts.data );
				opts.data = null;
			} else {
				opts.data = jLim.param( opts.data );
			}
		}

		// set request
		xhr.open( opts.type, opts.url, opts.async );
		xhr.setRequestHeader( 'Content-type', opts.contentType );

		if ( opts.dataType && opts.accepts[ opts.dataType ] )
			xhr.setRequestHeader( 'Accept', opts.accepts[ opts.dataType ] );

		// set sync or async call
		if ( opts.async ) {
			xhr.onreadystatechange = ready;
			xhr.send( opts.data );
		} else {
			xhr.send( opts.data );
			ready();
		}

		return xhr;
	},

	/**
	 * Ajax GET request
	 * @param {string} url
	 * @param {string|object} data Optional, containing GET values
	 * @param {function} success Optional, callback when request was succesfull
	 * @return {XMLHttpRequest|ActiveXObject}
	 */
	get: function( url, data, success ) {
		if ( jLim.isFunction( data ) ) {
			success = data;
			data = null;
		}

		return jLim.ajax({
			url: url,
			type: 'GET',
			data: data,
			success: success
		});
	},

	/**
	 * Ajax POST request
	 * @param {string} url
	 * @param {string|object} data Optional, containing post values
	 * @param {function} success Optional, callback when request was succesfull
	 * @return {XMLHttpRequest|ActiveXObject}
	 */
	post: function ( url, data, success ) {
		if ( jLim.isFunction( data ) ) {
			success = data;
			data = null;
		}

		return jLim.ajax({
			url: url,
			type: 'POST',
			data: data,
			success: success
		});
	},


	/**
	 * Make querystring outof object or array of values
	 * @param {object|array} obj Keys/values
	 * @return {string} The querystring
	 */
	param: function ( obj ) {
		var s = [];

		jLim.each( obj, function( k, v ){
			s.push( encodeURIComponent( k ) +'='+ encodeURIComponent( v ) );
		});

		return s.join( '&' );
	},

	/**
	 * Parse JSON string
	 * @param {string} data
	 * @return {object} JSON object
	 */
	parseJSON: function ( data ) {
		if ( typeof data !== 'string' || ! data )
			return null;

		return eval( '('+ jLim.trim( data ) +')' );
	}

});

