50 важни момента от историята на Интернет Announcements, Releases
Oct 04

Днес имах малко свободно време и реших да погледна малко един проект, и по-точно javascript честа от него. И се спрях на един простичък Tab панел, нищо особено, но нещо не ми хареса как изглеждаше JavaScipt-а. HTML изглежда така:


<div class="tab-panel">
	<ul class="tab-header">
		<li class="selected">tab 1</li>
		<li>tab 2</li>
		<li>tab 3</li>
	</ul>
	<div class="tab-content">tab 1 content</div>
	<div class="tab-content" style="display: none;">tab 2 content</div>
	<div class="tab-content" style="display: none;">tab 3 content</div>
</div>

За да направя това таб панел използвах просто тази функция:


function tabulize(panel){
	// слагам div.tab-content вместо .tab-content,
	// защото когато селектора има само клас селектира всички елементи и ги проверява,
	// а ако се сложи div.tab-content се селектират се всички div-ове и те се проверяват,
	// което е по-бързо
	var elements	= panel.select('div.tab-content'),
		buttons		= panel.down('ul').select('li');

	// дефинирам тази функция тук,
	// защото искам да имам достъп до elements, buttons
	function activate(item, key){
		// тук 1во се маха клас selected от всички бутони,
		// и се скриват всичките div.tab-content
		buttons.invoke('removeClassName', 'selected');
		elements.invoke('hide');

		// после се показва избрания елемент и се слага клас selected на избрания бутон
		elements[key].show();
		item.addClassName('selected');
	}

	// на всеки бутон се добавя eventhandler за click,
	// който ще подава на activate избрания елемент и неговия номер във buttons масива
	buttons.each(function(item, key){
		item.observe('click', activate.curry(item, key));
	});
}

Това е доста простичко, но и трудно за промени като динамично добавяне на съдържание, ефекти и други. Затова реших да го направя малко по-OOП, като запазя основните идеи:


var TabPanel = Class.create({
	// това е конструктора
	initialize: function(panel){
		panel = $(panel);
		// избираме отново elements и buttons, но ги записваме като инстанс променливи
		this.elements = panel.select('div.tab-content');
		this.buttons = panel.down('ul').select('li').each(function(){
			// единствената разлика е че active не е private функция и инстанс метод
			// и за това тук ползваме bind, а не curry
			item.observe('click', this.activate.bind(this, item, key);
		}.bind(this));
	},
	// active, вече е метод и само "this." e разликата
	activate: function(item, key){
		this.buttons.invoke('removeClassName', 'selected');
		this.elements.invoke('hide');
		this.elements[key].show();
		item.addClassName('selected');
	}
});

Сега става малко по-тежко, но и доста по-extendable и податливо на бъдещи промени. Но тук видях нещо, което пак не ми се хареса е че колкото таб бутони имам толкова пъти и в двете версии викам observe и реших да видя как ще изглежда това, като добавим малко event-delegation, за което трябваха само две промени:


var TabPanel = Class.create({
	initialize: function(panel){
		panel			= $(panel);
		this.elements	= panel.select('div.tab-content');
		this.buttons	= panel.down('ul').select('li');
		// първо променяме тук
		// слагаме click eventhandler на ul (списъка с бутоните)
		// вътре проверяваме дали li елемента, който сме натиснали (ако има такъв)
		// е във масива с бутоните, ако е така активираме табулацията с този номер
		panel.down('ul').observe('click', function(e){
			var key = this.buttons.indexOf(e.findElement('li'));
			if (key != -1) this.activate(key);
		}.bind(this));
	},
	// второто нещо което променяме е тук
	// махаме item параметъра, и на го заменяме с this.buttons[key]
	activate: function(key){
		this.buttons.invoke('removeClassName', 'selected');
		this.buttons[key].addClassName('selected');
		this.elements.invoke('hide');
		this.elements[key].show();
	}
});

И поне на този етап съм доволен, при нужда мога да добавя destroy, addTab и други неща :) А как инстансирам TabPanel-а ? Ами със CD3.Behaviors:


CD3.Behaviors({
	'div.tab-panel': TabPanel
});

Leave a Reply

  • Enter this code