Преди малко по-малко от година в един пост озаглавен “Малко JavaScript refactoring“, бях описал как работи прост таб панел. Части от него използвам и до сега. Но е време за промяна.
Това което не ми допада в тогавашната версия е, че когато в страницата се добавя нов таб панел трябва да се вика отново TabPanel. А ако трябва да се трие нещата стават доста сложни. Също така и добавянето и/или изтриването на табчета става доста трудно. Затова реших да напиша нещо малко по-бързо и по-лесно.
В последните ми няколко проекта по които работя, ще поддържат само Firefox 3.5 и Safari 4, и нагоре. Браузъри като IE (6/7/8) и даже Opera, въобще не влизат в картинката. Това ми дава възможност да се насладя на работата със html5 / css3. Затова структурата на един таб панел изглежда така:
<div class="tab_panel"> <nav> <ul> <li class="tab selected">tab 1</li> <li class="tab">tab 2</li> <li class="tab">tab 3</li> </ul> </nav> <section></section> <section style="display: none;"></section> <section style="display: none;"></section> </div>
А javascript, който задвижва нещата е:
function changeTab(){
this.up('.tab_panel').select('section').invoke('hide')[this.previousSiblings('.tab').length].show();
this.up().select('.selected').invoke('removeClassName', 'selected');
this.addClassName('selected');
}
document.delegate('.tab_panel .tab', 'click', changeTab);
Как работи кода?
Под ‘this’ се има предвид избрания таб. На ред първи, се “качваме” до елемента, съдържащ таб панела и крием всички ‘section’ елементи. И след това показваме само избрания от нас елемент. Като this.previousSiblings(‘.tab’).length, ни връща колко таб бутна има преди избрания таб, което е и индекса на избрания елемент.
На втори ред. Просто избирам всички елементи който имат клас “selected” (което отбелязва кое е избрания таб) и махам този клас от тях. Тук само искам да отбележа, че бих могъл да ползвам Element.down и после само Element.removeClassName на един елемент. Но съм предпочел да взема малко по “jquery” решение на въпроса.
И третия последен ред е просто слага ‘selected’ клас на избрания таб, маркирайки го като активен.
И след това моят Event.delegate, слуша за натискания на таб бутна и след това просто стартира changeTab.
На фокус
До преди няколко дни използвах този код при click събитие, но напоследък след по-голямото популяризиране на focus:in/focus:out и атрибута tabindex реших и да пробвам нещо малко “по-нестандартно” и така да направя таб панела достъпен и чрез “tab” бутона.
<div class="tab_panel"> <nav> <ul> <li class="tab selected" tabindex="1">tab 1</li> <li class="tab" tabindex="2">tab 2</li> <li class="tab" tabindex="3">tab 3</li> </ul> </nav> <section></section> <section style="display: none;"></section> <section style="display: none;"></section> </div>
function changeTab(){
this.up('.tab_panel').select('section').invoke('hide')[this.previousSiblings('.tab').length].show();
this.up().select('.selected').invoke('removeClassName', 'selected');
this.addClassName('selected');
}
document.delegate('.tab_panel .tab', 'focus:in', changeTab);
Най-хубавото тук е че ‘click’ не ни трябва защото при натискане на елемент-а focus събитието се изстрелва така или иначе. Също така ако не искате се чудите за tabindex-а в какъв ред да е, просто може да се сложи навсякъде да е 1.
Примерен таб панел съм качил тук. Той изисква 2 неща (освен Prototype.js) – Event.delegate и focus:in.
За момента съм го тествал само в Firefox 3.5 / Safari4 и работи страхотно. За другите браузъри не би трябвало да има проблеми, но ако някои има проблеми с удоволствие бих помогнал.

September 2nd, 2009 at 10:04 am
Специално за таб панелите, мисля, че трябва да са достъпни (accessible) т.е. да са с линк в LI елемента, който да води към анкор в някой от следходните DIV / SECTION. Всъщност, щом е HTML 5, може и LI-то да има href.
Така скрипта се променя, че делегираш клик / фокус върху линковете и намираш елемента по търсения анкор. Друг вариант е да делегираш (слушаш, ако делегирането не е възможно) # събитие т.е. когато фокуса дойде върху даден DIV / SECTION да го покажеш и да скриеш предния (референция към който се пази в TAB обекта на инстанцията).
Разбираемо ли го написах?
Когато се прибера, мога да ти пусна снипет на първия вариант с линковете. Работи безотказно.
– Иван
September 2nd, 2009 at 10:13 am
Също, ако някои неща ги допуснеш — примерно, че UL е първия child (не Node) на таб панела — скрипта става по-лек.
September 2nd, 2009 at 10:46 am
Да може доста добре може да стане, ще съм ти благодарен ако ми пуснеш връзка към снипет с твоя вариант
Ако имаш github акаунт може направо да fork-неш този gist – http://gist.github.com/179320
September 2nd, 2009 at 4:29 pm
Това е бързия вариант — http://gist.github.com/179708
Ако се приеме, че се работи с ХТМЛ 5, то може да се работи с querySelector, което опростява селекторите. Ще го променя като се прибера.
September 2nd, 2009 at 11:53 pm
Коментари за това — http://gist.github.com/179719 — slice(0), а не 1, както го бях написал е нужен за да премахнеш първия сиблинг от масива т.е. навигацияат. Иначе рискуваш да скриеш всички елементи
September 3rd, 2009 at 1:34 am
Ами доста добре става
само дефинирането не многото event handler-и не ми допада много, а и focus:in още може да ползва
Това за сега е оптималния вариант и все още е четири реда: http://gist.github.com/180007 ( Утре сигурно ще го вмъкна като update на поста ), благодаря за помощта
September 3rd, 2009 at 2:17 am
Това навързване на методите го направих заради майтапа — честно казано ми се струва по-правилно първо да се скрият другите елементи и след това да се покаже текущия, а не обратно. Допълнително, предпочитам да си разписвам методите на нови редове — ей така да не се губя.
Но от друга страна това действие отнема няколко тика и е на практика неусетно. И не на последно място — така е много по-кратко или поне създава такова усещане.
П.П.
Принципно цъкам основно ЦСС, а ДжаваСкрипта ми е хоби, така че като пиша снипети ги проверявай за грешка.