Показать сообщение отдельно
  #1 (permalink)  
Старый 11.03.2014, 21:30
_0_ _0_ вне форума
Аспирант
Отправить личное сообщение для _0_ Посмотреть профиль Найти все сообщения от _0_
 
Регистрация: 10.05.2013
Сообщений: 56

О делегировании в DOM
Скажу честно, нахожусь в долгом свободном поиске идеальной библиотеки web-компонентов, пересмотрел большое количество оных, многие, мне как начинающему, с первого взгляда казались очень громоздкими, другие слишком сложные, третьи...

Для меня идеал - браузерная javascript библиотека по структуре похожая на .Net. Таковой нет. Но в последнее время стал думать о компонентах на основе делегирования, на такие мысли меня навела библиотека CornerJS, к сожалению, автор не достаточно описал свою библиотеку, но идею я понял и решил создать "велосипед" на свой лад, писал код около 2-х часов, возможны ошибки и недочеты, т.к. это не prodaction, а просто пример делегированных компонентов.

Основные директивы подхода:
1. Все обработчики "вешаются" на body, ну разумеется, в целях производительности, mousemove "вешать" не стоит.
2. Объекты-делегаторы не вешают своих обработчиков, главный объект, при наличии события ищет делегатор и пытается вызвать у него соответствующий событию метод.
3. Главный объект умеет создавать новый dom-компонент из шаблона, при создании, вызывает у его делегатора (и вложенных) метод init().

Плюсы:
- малое количество обработчиков, все "вешаются" на body
- встроенное делегирование событий
- делегаторы, при желании, можно в прямом смысле наследовать через прототип
- использование шаблонов разметки, можно придумать механизм предварительной обработки шаблонов (вставки, псевдо-наследования, сложения аттрибутов ...)
- легкое создание и удаление компонентов из dom
- мизерный движок
- IE8 отдыхает

Минусы:
- возможны тормоза, связанные с обработкой событий, обусловлено поиском делегатора по dom и необходимостью "всплытия" события до body, но это надо проверить
- сложность реализации модульности, компонент состоит из 3-х частей: стиля, разметки и самого js кода, придется думать, как все это "запихать" в один js-файл
- IE8 отдыхает

Предлагаю обсудить подход и реализацию.

Ну вот и сам код, пример окна диалога:
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<title>Delegation</title>
	
	<style>
		
		[template] {
			display:none;
		}
		
		.dialog {
			position:absolute;
			left:100px;top:100px;
			width:300px;height:250px;
			border:1px solid gray;
			background-color:#fff;
		}
		.dialog  > .container {
			position:relative;
			top:0px;left:0px;right:0px;bottom:0px;
		}
		
		.dialog > .container > .close-button {
			position:absolute;
			right:3px;top:3px;
		}
		
		.dialog > .container > .title {
			cursor:default;
			position:absolute;
			top:0px;
			left:0px;right:0px;
			height:20px;
			line-height:20px;
			padding-left:20px;
			overflow:hidden;
			background-color:#C7E2FA;
			border-bottom:1px solid gray;
		}
		
		.dialog > .container > .content {		
			position:absolute;
			top:20px;bottom:0px;
			left:0px;right:0px;			
		}
		
		
		
	</style>
	
</head>
<body>
<button onclick='add_dialog()'>Нажми</button>
<script>
function add_dialog(){
	Delegator.create( 'dialog' );
}
</script>
<script>

var Delegator = {
	
	events:['click','mousedown','mouseup'],
	
	start:function(){
		
		var _this = this;
		var handler = function(ev){
			return _this.on_event(ev);
		}
		for( var i=0,l=this.events.length; i<l; i++ ){
			document.body.addEventListener( this.events[i], handler, false );
		}
		
	},
	
	on_event:function(ev){
				
		console.log( ev.type );
		
		var top = document.body;
		
		var target = ev.target;
		var type = ev.type;
		
		var delegate_target = null, action_target = null, value_target = null;
		var delegate = null, caction = null, cvalue = null;
		
		// поиск делегатора, а за одно действие caction и данные cvalue
		
		do{
			
			if( !cvalue && target.hasAttribute('cvalue') ){
				cvalue = target.getAttribute('cvalue');
				value_target = target;
			}
			
			if( !caction && target.hasAttribute('caction') ){
				caction = target.getAttribute('caction');
				action_target = target;
			}
			
			if( target.hasAttribute('delegate') ){
				delegate = target.getAttribute('delegate');
				delegate_target = target;
				break;
			}
			
		}while( target != top && ( target = target.parentElement ) );
		
		if( delegate ){	
			if( this.delegators[delegate] ){
				
				var obj = this.delegators[delegate];
				
				// дополнение события своими данными
				ev.delegate = delegate;
				ev.delegate_target = delegate_target;
				ev.caction = caction;
				ev.action_target = action_target;
				ev.cvalue = cvalue;
				ev.value_target = value_target;
				
				var method = null;
				var method1 = 'on_'+type; // например on_click
				var method2 = caction ? ( method1+'_'+caction ) : null; // например on_click_title
				var method3 = ( method2 && cvalue ) ? ( method2+'_'+cvalue) : null; // например on_click_button_close
				
				if( typeof(obj[method3]) == 'function' ){
					method = method3;
				}else if( typeof(obj[method2]) == 'function' ){
					method = method2;
				}else if( typeof(obj[method1]) == 'function' ){
					method = method1;
				}
				
				if( method ){
					return obj[method]( delegate_target, ev );
				}
			}else{
				console.log('Не найден делегатор '+delegate);
			}
		}
		return false;
	},
	
	delegators:{},
	
	create:function( template , parent_element ){
		parent_element = parent_element || document.body;
		var element = document.querySelector('[template='+template+']');
		if(element){
			element = element.cloneNode(true); // подмена на клон
			element.removeAttribute('template'); // удаляем флаг шаблона
			
			parent_element.appendChild( element );
			
			var all=[]; // запись всех делегируемых элементов в один массив
			if( element.hasAttribute('delegate') ) all.push( element );
			var nodes = element.querySelectorAll('delegate');
			if( nodes && nodes.length ) for( var i=0,l=nodes.length; i<l; i++ ) all.push(nodes[i]);
			
			// поиск метода init и запуск
			for( var i=0,l=all.length; i<l; i++ ){
				var node = all[i];
				var delegate = node.getAttribute('delegate');
				if( this.delegators[delegate] ){
					var obj = this.delegators[delegate];
					if( typeof(obj.init) == 'function' ){
						obj.init( node );
					}
				}else{
					console.log('Не найден делегатор '+delegate);
				}
			}
			
		}else{
			console.log('Не найден шаблон '+template);
		}
	}
}

Delegator.start();

	
</script>


<div template='dialog' class='dialog' delegate='dialog'>
	<div class='container'>
		<div class='title' caction='title' >title</div>
		<div class='content'>Container
			<hr />
			<button caction='button' cvalue='add'>Добавить дочернее окно</button>
		</div>
		
		<button caction='button' cvalue='close' class='close-button'>X</button>
	</div>
</div>
<script>
/* делегатор для dialog */
Delegator.delegators.dialog = {
	
	init:function( element ){
		console.log('Элемент dialog создан');
		this.to_top( element );
	},
	
	on_mousedown_title:function( element, ev ){
		
		this.to_top( element );
		
		var dx = ev.pageX - element.offsetLeft;
		var dy = ev.pageY - element.offsetTop;
		
		var move = function( ev ){
			element.style.left = ( ev.pageX - dx ) + 'px';
			element.style.top  = ( ev.pageY - dy ) + 'px';
			return false;
		}
				
		var stop = function( ev ){
			document.removeEventListener( 'mousemove' , move , true );
			document.removeEventListener( 'mouseup' , stop , true );
			element = move = dx = dy = stop = null;
			return false;
		}
		
		document.addEventListener( 'mousemove' , move , true );
		document.addEventListener( 'mouseup' , stop , true );
		
		return false;
	
	},
	on_click_button_close:function( element, ev ){
		this.destroy( element );
		return false;
	},
	on_click_button_add:function( element ){
		Delegator.create( 'dialog' , element.querySelector('.container') );
		return false;
	},
	on_click:function( element , ev ){
		this.to_top( element );
		return false;
	},
	to_top:function(element){
		var parent = element.parentElement;
		if( !parent.hasAttribute('dialog-z-index') ){
			parent.setAttribute('dialog-z-index',10);
		}
		var z = 1 + parseInt(parent.getAttribute('dialog-z-index'));
		parent.setAttribute('dialog-z-index',z);
		element.style.zIndex = z;
		console.log( z );
	},
	destroy:function(element){
		element.parentElement.removeChild( element );
	}
}


</script>

</body>
</html>
Ответить с цитированием