Днес имах малко свободно време и реших да погледна малко един проект, и по-точно 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
});

