/**
 * Корневая библиотека системы виджетов
 * @package		sdf
 * @subpackage	widget
 * @since      16.11.2007 16:30:00
 * @author     Davojan
 */

//{{{ $sdf
/**
 * Достаёт sdf-Объект из соответствующего jQuery-запроса
 * @param mixed selector селектор
 * @param mixed context контекст
 * @param boolean init произвести инициализацию, если элемент не найден
 * @return sdf
 */
function $sdf( selector, context, init )
{
	var j = jQuery(selector, context);
	init = typeof init == 'undefined' ? false : true;
	if ( j.length )
	{
		var res = jQuery.data(j[0], 'sdf');
		if ( res || ! init ) {
			return res;
		}
		j.sdf();
		return jQuery.data(j[0], 'sdf');
	}
	else {
		return null;
	}
}
//===========================================================================}}}

(function($){

	$.sdf = $.sdf || {};
	var sdf;

//{{{ Расширение jQuery

//{{{ sdfHandle
/**
 * Регистрирует события, которые должны обрабатываться sdf-объектом
 */
	$.fn.sdfHandle = function()
	{
		var i = 0;
		for (; i < arguments.length; i++ )
		{
			if ( arguments[i].constructor === Array ) {
				this.bind( arguments[i][0], arguments[i][1], function(Evt){sdf.handle.apply(this,[Evt]);} );
			}
			else {
				this.bind( arguments[i], function(Evt){sdf.handle.apply(this,[Evt]);} );
			}
		}
		return this;
	};
//===========================================================================}}}
//{{{ sdf
/**
 * Привязывает элементы из jQuery-набора к заданному виджет-классу
 * @param $.sdf cl класс
 * @param bool strict плеваться ли исключением в случае повторного привязывания? по умолчанию - да
 */
	$.fn.sdf = function( cl, strict )
	{
		strict = typeof strict == 'undefined' ? true : false;
		cl = typeof cl == 'undefined' ? false : cl;
		for ( var i = 0; i < this.length; i++ )
		{
			if ( strict || ( $.data( this[i], 'sdf' ) == null ) )
			{
				if ( cl ) {
					new cl( this[i] );
				}
				else
				{
					var $i = $( this[i] );
					$i.is( '.sdf-input' ) && new $.sdf.input( this[i] );
				}
			}
		}
		return this;
	};
//===========================================================================}}}

//===========================================================================}}}

//{{{ $.sdf
/**
 * Класс-предок
 */

//{{{ sdf
/**
 * Конструктор
 * @param Element el элемент
 */
	$.sdf = function( el )
	{
		if ( el )
		{
			if ( el != '__prototype__' )
			{
				this.__clean__ = new Array();
				this.init( el );
			}
			else {
				return false;
			}
	  	}
	  	else {
			this.__clean__ = new Array();
	  	}
		sdf.__clean__.push( this.__clean__ );
		this.uniqId = Math.round( Math.random()*1000000000 );
		this.eventStoreProp = $.browser.mozilla || $.browser.msie ? 'returnValue' : 'initUIEvent';
	};
	sdf = $.sdf;
//===========================================================================}}}

// Статика
$.extend( sdf, {

	__clean__: new Array(),
//{{{ extend
/**
 * Эмуляция наследования
 *
 * @param $.sdf parent класс-предок, по умолчанию - текущий класс
 * @param Function child конструктор, по умолчанию - пустой
 * @param Object prototypeExtent объект-расширитель прототипа
 * @param Object classExtent объект-расширитель класса
 * @return $.sdf
 */
	extend: function()
	{
		var ei = 1, parent = arguments[0] || {}, child;
	// если предок не указан, то предок - текущий класс
		if ( ! ($.isFunction( parent ) && parent.prototype && sdf.prototype.isPrototypeOf( parent ))  )
		{
			parent = this;
			ei = 0;
		}
	// если конструктор не указан, то создаём конструктор по-умолчанию, который вызывает конструктор предка
		if ( arguments.length > ei && $.isFunction( arguments[ei].__construct ) )
		{
			child = arguments[ei].__construct;
			arguments[ei].__construct = undefined;
		}
		else {
			child = function() { if ( !arguments.length == 0 || arguments[0] != '__prototype__' ) { this.parentCall('constructor', arguments, parent); } };
		}
	// добавляем в класс методы и свойства предка
		$.extend( child, parent );
		child.prototype = new parent( '__prototype__' );
		child.prototype.parent = parent;
		child.prototype.constructor = child;

	// расширяем прототип (динамику)
		if ( arguments.length > ei ) {
			$.extend( child.prototype, arguments[ei] );
		}
	// расширяем статику
		if ( arguments.length > ei + 1 ) {
			$.extend( child, arguments[ei + 1] );
		}

		return child;
	},
//===========================================================================}}}
//{{{ handle
/**
 * Обработчик-редиректор событий
 * @param Event Evt событие
 */
	handle: function( Evt )
	{
		var obj = Evt.data && Evt.data.obj ? Evt.data.obj : $.data( this, 'sdf' );
		var type = Evt.data && Evt.data.type ? Evt.data.type : Evt.type;

		var args = [];
		for ( var i = 0, count = arguments.length; i < count; i++ ) {
			args[i] = arguments[i];
		}
		args.splice( 1, 0, this );

		if ( $.isFunction( type ) ) {
			return type.apply( obj, args );
		}
		else
		{
			var hName = 'on' + type.substr(0,1).toUpperCase() + type.substr(1);
			if ( $.isFunction( obj[hName] ) ) {
				return obj[hName].apply( obj, args );
			}
		}
	},
//===========================================================================}}}
//{{{ cleanLeaks
/**
 * Очищает утечки памяти в шестом
 */
	cleanLeaks: function()
	{
		var i, j;
		for ( i = 0; i < sdf.__clean__.length; i++ )
		{
			if ( sdf.__clean__[i] !== null )
			{
				for ( j = 0; j < sdf.__clean__[i].length; j++ ) {
					$.removeData(sdf.__clean__[i][j], 'sdf');
				}
				sdf.__clean__[i] = null;
			}
		}
		sdf.__clean__ = null;
	}
//===========================================================================}}}

});

sdf.prototype = {

	constructor: sdf,
	__clean__: null,
	__cur_context__: null,

//{{{ bind
/**
 * Биндит обработчик к какому-либо событию объекта
 * @param String et тип события
 * @param Function handler обработчик события, по умолчанию - sdf.handle
 * @param Array data дополнительные данные
 */
	bind: function( et, handler, data )
	{
		if ( ! $.isFunction( handler ) )
		{
			data = handler;
			handler = sdf.handle;
		}
		return $.event.add( this, et, handler, data );
	},
//===========================================================================}}}
//{{{ bindObj
/**
 * Биндит обработчик-метод заданного объекта к какому-либо событию объекта
 * @param String et тип события
 * @param Object obj объект, метод которого нужно вызвать
 * @param String redirEt тип события у целевого объекта
 */
	bindObj: function( et, obj, redirEt )
	{
	// TODO Функция работает некорректно при бинде на один объект два обработчика одного и того же события (тикет #1313)
		redirEt = redirEt || (obj && obj.constructor === String ? obj : et);
		obj = obj || this;
		obj = obj.constructor === String ? this : obj;
		return $.event.add( this, et, sdf.handle, {obj:obj, type:redirEt} );
	},
//===========================================================================}}}
//{{{ trigger
/**
 * Активирует событие заданного типа
 * @param String et тип события
 */
	trigger: function( et, data, extra )
	{
		$.event.trigger( et, data, this, false, extra );
	},
//===========================================================================}}}
//{{{ parentCall
/**
 * Вызывает метод предка в контексте потомка
 * @param string method название метода
 * @param array params параметры
 * @param Object cl класс, метод которого надо вызвать, по умолчанию используется родитель
 * @return mixed
 */
	parentCall: function( method, params, cl )
	{
		var cur = this.__cur_context__, res;
		params = params || [];
		if ( ! cl )
		{
			if ( this.__cur_context__ === null ) {
				cl = this.parent;
			}
			else {
				cl = this.__cur_context__.prototype.parent;
			}
		}
		this.__cur_context__ = cl;
		if ( this.parent.prototype[method] )
		{
			res = cl.prototype[method].apply( this, params );
			this.__cur_context__ = cur;
			return res;
		}
		else {
			alert( 'invalid method' );
		}
	},
//===========================================================================}}}
//{{{ instanceOf
/**
 * Проверяет, является ли объект экземпляром заданного класса
 * @param Object cl проверяемый класс
 * @return bool
 */
	instanceOf: function( cl )
	{
		return cl.prototype.isPrototypeOf( this );
	},
//===========================================================================}}}
//{{{ init
/**
 * Инициализирует основной элемент виджета
 * @param Element el элемент
 * @return jQuery
 */
	init: function( el )
	{
		return this.bindElement( 'el', el );
	},
//===========================================================================}}}
//{{{ bindElement
/**
 * Прикрепляет dom-элемент к sdf-объекту
 * Создаёт в объекте поля <имя> и <$имя> (соответсвующий элементу jQuery-объект)
 * @param String name название поля
 * @param mixed selector селектор
 * @param mixed context контекст
 * @return jQuery
 */
	bindElement: function( name, selector, context )
	{
		var j;
		if ( name.constructor !== String )
		{
			selector = name;
			name = null;
		}
		if ( selector.constructor !== String )
		{
			context = null;
			if ( selector.styleFloat ) {
				j = selector;
			}
			else {
				j = $(selector);
			}
		}
		else {
			j = $(selector, context);
		}

		if ( j.length )
		{
			if (name) {
		 		this[name] = j[0];
		 	}
			if ( $.data(j[0], 'sdf') != null ) {
				throw new Error('sdf data slot is already occupied');
			}
			$.data(j[0], 'sdf', this);
			this.__clean__.push( j[0] );
		}
		else
		{
			if (name) {
		 		this[name] = null;
		 	}
		}
		if ( name ) {
			this['$'+name] = j;
		}

		return j;
	},
//===========================================================================}}}
//{{{ addEventListener
/**
 * Заглушка, чтобы можно было использовать механизм событий из jQuery
 */
	addEventListener: function(){},
//===========================================================================}}}
//{{{ drop
/**
 * Очищает объект, вызывает деструктор
 * Этот метод не должен переопределяться
 */
	drop: function()
	{
	// дабы избежать бесконечной рекурсии...
		if ( ! this.__dropping__ )
		{
			this.__dropping__ = true;
			this.__destruct();
			var i;
			for ( i = 0; i < this.__clean__.length; i++ ) {
				$.removeData(this.__clean__[i], 'sdf');
			}
			this.__clean__ = null;
			if ( typeof this.$el !== 'undefined' ) {
				this.$el.remove();
			}
		}
	},
//===========================================================================}}}
//{{{ __destruct
/**
 * Деструктор
 * Очищает всякие связи, чтобы не дай бог утечек не было
 * Этот метод может переопределяться наследниками, чтобы задавать дополнительные очистки
 */
	__destruct: function() {},
//===========================================================================}}}
//{{{ set
/**
 * Устанавливает свойства объекта. Может вызываться как this.set( name, value ),
 * так и this.set( {name1: value1, name2:value2, ..., namen: valuen} )
 */
	set: function()
	{
		var o = {}, a = arguments, b = a.length;
		if ( b === 2 ) {
			o[a[0]] = a[1];
		}
		else if ( b === 1 ) {
			o = a[0];
		}
		for ( var i in o ) {
			this[i] = o[i];
		}
		if ( this.afterSet && typeof (this.afterSet) === 'function'  ) {
			this.afterSet();
		}
	},
//===========================================================================}}}
//{{{ stopPropagation
/**
 * Прекратить обрабатывать этот event данным объектом
 */
	stopPropagation: function( Evt )
	{
		Evt = Evt.originalEvent || Evt;
		if ( ! Evt ) {
			return;
		}
		if ( typeof( Evt[this.eventStoreProp] ) != 'object' ) {
			Evt[this.eventStoreProp] = {};
		}
		if ( Evt[this.eventStoreProp][this.uniqId] ) {
			Evt[this.eventStoreProp][this.uniqId].cancelBubble = 1;
		}
		else
			Evt[this.eventStoreProp][this.uniqId] = { cancelBubble:1 };
	},
//===========================================================================}}}
//{{{ cancelBubble()
/**
 * Нужно ли прервать обработку данного события
 */
	cancelBubble: function( Evt )
	{
		Evt = Evt.originalEvent || Evt;
		return Evt[this.eventStoreProp] && Evt[this.eventStoreProp][this.uniqId] && Evt[this.eventStoreProp][this.uniqId]['cancelBubble'];
	}
	//===========================================================================}}}
};
	if ( $.browser.msie && $.browser.version.indexOf('6.') !== -1 ) {
		$(window).bind("unload", sdf.cleanLeaks );
	}
//===========================================================================}}}

})(jQuery);

/*============================================================================*
 * vim: set expandtab tabstop=3 shiftwidth=3 foldmethod=marker:               *
 *   END OF FILE                                                              *
 *============================================================================*/