Меню

Техники за напреднали

Влез Излез

Tabs – част 2 – Deep Linking

В предния урок показах как можем с помощта на jQuery UI да добавим tabs widget в някоя HTML страница. Това, което ще добавим сега е deep linking за отделните табове.

Като за начало аз лично съм сменил съдържанието на отделните табове, за да разграничаваме по-лесно, че в момента сме на различен таб.

В наша подкрепа се явява факта, че jQuery Tabs Widget-а е написан достатъчно добре, така че ако URL-ът на страницата съдържа „?tab=#ID“, widget-а сам ще потърси дали няма панел, чието id да е „ID“ и ако такъв панел съществува – той автоматично ще бъде показан, без да ни се налага на нас да викаме методи, които да го активират.

Така задачата ни се свежда до добавяне на „tab“ GET агрумент в URL-а на страницата, чиято стойност да е „#“ последван от id-то на панела (в общи лини се получава CSS селектор по ID).

Първият начин да постигнем това е с rel атрибут със синтаксис, който jQuery Address да отчете. Промяната се свежда до елементарен update на markup-а ни:

<div id="tabs">
	<ul>
		<li><a href="#first" rel="address:/tabs.html?tab=#first">First</a></li>
		<li><a href="#second" rel="address:/tabs.html?tab=#second">Second</a></li>
		<li><a href="#third" rel="address:/tabs.html?tab=#third">Third</a></li>
	</ul>
	...
</div>

Ако стойността на rel атрибута на някой линк започва с „address:“, то jQuery Address ще се погрижи address bar-ът на браузъра ни да се update-не до стойността, която сме изписали след това. В нашия случай това трябва да съдържа „tabs.html“, защото предварително сме задали основата на URL адреса, с която jQuery Address работи да е root адреса на страницата ни (който не включва адреса на страницата tabs). След това трябва да добавим въпросителен знак, отделящ GET аргументите в адреса. Накрая трябва да зададем и двойтака oroperty-value за GET агрумента, който искаме да добавим.

Макар и това да е най-лесният начин, за който се сещам в момента, на мене не ми харесва да задавам произволни стойности в rel атрибут, тъй като по спецификация, този атрибут може да съдъжа точно определени стойности (в които не влиза нашия адрес).

Друга причина е, че при активен tab, линковете за табовете получават активно състояние и с използване на стрелките за наляво и надясно можем да променим активния таб. Това е част от accesability възможностите на jQuery UI Tabs. При промяна на активния tab по този начин обаче, ние не кликаме на линка, съответно jQuery Address не обновява стойността на адреса на браузъра.

На всичко отгоре на мене лично не ми харесва да трябва да изписвам името на страницата (tabs.html).

По тази причина бих предпочел да направя обновяването чисто с JavaScript.

Това, което трябва да направим е по някакъв начин да засечем кога табовете се сменят и в този момент да обновим адреса на браузъра. Засичането на смяната на табовете можем да извършим като използваме event-ите на jQuery UI Tabs widget-а. Ако потърсите в интернет лесно можете да намерите пълната документация на jQuery UI Tabs Widget. В секцията events ще видите acivate, който е event, изпълняващ се при активиране на нов таб. Точно това ни трябва и на нас.

Handler-и за jQuery UI Widget-и се задават като property-та на инициализационен обект:

$('#tabs').tabs({
	activate: function (e, ui) {
		// add deep linking state
	}
});

От документацията виждаме, че при извикването на функцията се подават два параметъра – event и ui. Първиятт съдържа основна информация за самия activate event, а вторият съдържа информация относно самия widget – кой е бил предишно активния таб, кой е новият активен и т.н.

Като стойност на property-то подаваме функция (не забравяйте, че в JavaScript функциите са просто тип обект), в която трябва да добавим нашия код, грижещ се за правилното обновяване на адреса на браузъра.

По принцип следния код би трябвало да работи:

$('#tabs').tabs({
	activate: function (e, ui) {
		$.address.parameter('tab', '#' + ui.newPanel.attr('id'));
	}
});

Това, което правим е, че при активиране на нов таб задаваме parameter (GET аргумент) със ключ „tab“ и стойност „#tabID“ (където „tabID“ е id-то на новия панел, който сме достъпили чрез аргумента ui).

Проблемът идва в това, че jQuery Address не засича правилно стойността на GET аргументи, които съдържат „#“, в следствие от което кодът се чупи. Ако в бъдеще този bug бъде поправен, то кодът ни ще функционира правилно, до тогава обаче трябва да намерим друго решение.

Можем да опитаме да подадем само стойността на ID-то на панела, без този „#“, но тогава пък възниква конфликт с jQuery UI Tabs, който не може да изпълни правилно селектора, без „#“.

При опит да заменим селектора с „div#ID“ jQuery UI Tabs работи правилно, но jQuery Address отново се чупи, поради наличието на „#“, макар и не в началото на параметъра.

Налага се да напишем малко повече код:

$('#tabs').tabs({
	activate: function (e, ui) {
		var currAddress = $.address.value();
		var tab = '#' + ui.newPanel.attr('id');
		var newAddress = '';
		
		if (currAddress.match(/\?/)) {
			var parameters = $.address.parameterNames();
			if ($.inArray('tab', parameters) == -1) {
				newAddress += '&tab=' + tab;
			} else {
				newAddress = currAddress.replace(/tab=#[^&]+/, 'tab=' + tab);
			}
		} else {
			newAddress = currAddress + '?tab=' + tab;
		}
		
		$.address.value(newAddress);
	}
});

Първо записваме текущия адрес в променливата currAddress за по-лесно достъпване в последствие.

След това в променливата tab се записваме стойността на GET аргумента, който трябва да добавим.

Създаваме празна променлива newAddress, в която по-късно ще запишем как трябва да изглежда новия адрес.

Правим проверка дали текущият адрес съдържа „?“ (дали вече има GET аргументи). След малко ще обясня какво правим ако има, нека само за момент прескочим до else-а. Ако няма GET аргументи – животът ни се улеснява и просто добавяме „?tab=#tabID“ към текущия адрес.

Ако обаче имаме GET аргументи първо взимаме списък с ключовете им чрез $.address.parameterNames(). След това правим проверка дали сред тези GET аргументи фигурира нашия (и трябва да го презапишем) или тези GET аргументи са други и трябва да добавим нашия.

Забележка: колкото и елементарно да звучи, резултатът от $.inArray() е доста хлъзгав. Функцията приема търсения елемент като първи аргумент и масив с елементи, в който да търсим като втори аргумент. Функцията връща като резултат или индексът на елемента (ако е намерен) с 0 индексиране, или „-1“, ако елементът липсва. Макар и да знам това, пишейки урока аз лично допуснах грешката да очаквам булева стойност като резултат от фунцията. Тъй като елементът беше първи в масива (индекс 0), условието не се изпълняваше, тъй като 0 се счита за равно на false, която беше в разрез с „правилния“ за мене резултат.

Ако GET аргументът „tab“ не присъства в текущиге GET аргументи, тогава просто добавяме „&tab=#tabID“ към адреса. В противен случай, с помощта на регулярен израз заменяме стойността на аргумента с ID-то на новия таб.

Ето и малко разяснения за регулярния израз: започваме засичането с обикновения текст „tab=#“. След това в квадратни скоби изреждаме знаци, които допускаме, че може да се съдържат. Ако обаче първият знак в квадратните скоби е „^“ (както е в нашия случай), значението на скобите се обръща, така че те намират всички знаци, без изброените. Въпросните „изброени“ знаци са просто „&“, тъй като той би отделил следващия GET аргумент. Знакът „+“ след квадратните скоби обозначава, че изброените знаци трябва да се срещат поне 1 или повече пъти.

Естествено, при IE са налице проблеми при функционалността. В скоро време ще оправим и тях.

За момента можете да видише demo на http://magadanski.com/demo/tabs-2-deep-linking/tabs.html

Вашият коментар

Вашият имейл адрес няма да бъде публикуван. Задължителните полета са отбелязани с *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax