Меню

Първи стъпки в правене на сайтове

Влез Излез

AJAX Заявки

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

Нека започнем с няколко готови страници, чиито статичен HTML и CSS вече съм написал. Разбира се можете да напишете и свой код, ако предпочитате – работните файлове са единствено с цел да спестят малко време за неща, които вече трябва да са ясни.

Нека все пак да кажем няколко думи за работните файлове. Те включват четири страници – Home (index.html), Tabs (tabs.html), Accordion (accordion.html) и Slider (slider.html). Разликите между стнаниците са минимални. Заглавието е отразено в <title> и <h2> таговете, като вторият е използван за заглавие вътре в страницата. Също така и класът current-menu-item е назначен на различни навигационни елементи в отделните страници.

Впечатление може да ви направи следният <link> таг:

<link href="http://fonts.googleapis.com/css?family=Open+Sans:300&amp;subset=latin,cyrillic-ext,cyrillic" rel="stylesheet" type="text/css" />

Той ни позволява в сайта си за използваме шрифт, който не е web safe. С този линк зареждаме външен CSS файл от сървърите на Google. В зависимост от версията му, на браузъра се предоставя web font с подходящ формат. Всички модерни (а и дори някои стари браузъри) поддържат web fonts – IE5.5+, Chrome, Firefox, Safari и Opera, така че не се притеснявайте да използвате web fonts. В скоро време ще напиша урок, който да дава повече разяснения по въпроса.

В стиловете си (style.css) нямаме почти нищо по-особено. За заглавията използвам шрифта Open Sans, който съм заредил от Google Fonts, а за HTML5 таговете съм добавил display: block, за да нямам проблеми с рендирането им от стари браузъри (IE 7/8).

В папката js ще намерите и func.js, който отново спомага за правилното обработване на HTML5 таговете в IE 7/8.

Това, което ще направим сега, е да зареждаме съдържанието на отделните страници, без реално да напущаме текущата. Това се постига чрез AJAX request-и.

AJAX е съкращение от Asynchronous JavaScript And XML (асинхронен JavaScript и XML – не че като се преведе внася яснота).

„Асинхронен JavaScript“ означава, че кодът се изпълнява в отделен процес на браузъра, което не кара страницата ни да замръзне. По време на изпълнение на синхронен код, браузърът не позволява никаква интеракция със съдържанието като скролване на страницата или кликване на линк. Прост пример за синхронен код е викането на alert() с JavaScript. Popup-ът, който излиза, блокира всичко останало на страницата, включително и изпълнението на по-нататъшни скриптове, докато Popup-ът не се затвори.

Каква е ролята на XML-а в цялата работа? Честно казано – можем дори да нямаме XML. Вече сме говорили, че това е просто език за маркиране без строго определени тагове, който е полезен за пренос на информация. AJAX всъщност работи с XML HTTP Requests (XML заявки изпратени по HTTP), който отначалото се е използвал, за да връща информация под формата на XML. Нищо обаче не ни ограничава за формата на данните, стига да можем да си ги обработим правилно.

Забележка: за да правите AJAX заявки трябва да имате инсталиран сървър на вашия компютър и да зареждате страниците през http://localhost. За начинаещи мога да препоръчам просто да си изтеглят и инсталират WAMP, а аз обещавам в скоро време да напиша урок във връзка с инсталирането на сървъри.

Това, с което трябва да започнем е да се вържем за кликването на линковете и да заредим по някакъв начин информацията от другите страници, без да презареждаме текущата:

jQuery(function ($) {
	$('nav a').click(function (e) {
		e.preventDefault();
	});
});

Първо селектираме линковете от nav тага и им назначаваме click event handler. Функцията, която се назначава към събитието, приема аргумент, съдържащ информация за самото събитие. В нашия случай това е променливата e. Викайки методът preventDefault() на този обект, ние предотвратяваме действието по подразбиране, което е именно браузърът да напусне текущата страница и да зареди новата.

В момента обаче кликайки на линковете не става нищо, а ние искаме да заредим новата информация. Нужно ни е да направим AJAX заявка, с която да вземем съдържанието:

jQuery(function ($) {
	$('nav a').click(function (e) {
		e.preventDefault();
		
		$.get($(this).attr('href'), function (data, status, XHR) {
			
		});
	});
});

В event handler функцията добавяме $.get. Функцията get() на jQuery ни позволява чрез обикновена GET заявка (това са заявките, които браузърите по принцип правят когато сареждат нова страница) да заредим съдържание от даден URL. Този URL се подава като първи аргумент на функцият. В нашия случай това е просто href атрибута на съответния линк. Обърнете внимание на елегантността, с която работим. Първо – нужно е URL-ът, от който ще зареждаме информацията да не е въведен в самия JavaScript, тъй като той трябва да е независим от markup-а, към който се прилага (поне доколкото можем). Второ – взимаме този URL именно от href отрибута на линка. Можехме да въведем това и като data атрибут или какво ли още не, но в случая е уместно да използваме href, тъй като ако потребителят случайно е изключил JavaScript-а си, то той да презареди страницата, така че в крайна сметка отново да достъпи същото съдържание, а това, че страницата се е презаредила е бял кахър, за който той си е виновен, че си е изключил JavaScript-а.

Вторият аргумент на $.get по принцип е обект с допълнителни аргументи, които бихме искали да изпратим с нашата GET заявка, но в случая нямаме нужда от това и подаваме направо третият аргумент, който е функция, която да се изпълни при получаване на резултат от заявката. Нужно е да направим това в функция именно поради асинхронността на кода. Тъй като заявката отнема време да получи своя отговор (първо трябва да достигне до сървъра, след това сървърът трябва да я обработни и да подготви резултат, а накрая трябва и този резултат да стигне обратно до нас), а ние не искаме страницата ни да замръзне за 200 милисекунди или пък – още по-лошо – 3-4 секунди, затова подготваме callback функция, която да бъде извикана щом получим резултат.

Callback функцията ни приема 3 аргумента. Първият е същинската информация, която сървърът е върнал. Вторият аргумент е ключова дума относно статуса на сървъра, тъй като нашата заявка може да върне празен отговор поради грешка от сървъра. Третият аргумент е XHR обект, съдържащ информация за заявката. XHR е съкращение от XML HTTP Request, за което споменахме малко по-рано.

jQuery(function ($) {
	$('nav a').click(function (e) {
		e.preventDefault();
		
		$.get($(this).attr('href'), function (data, status, XHR) {
			if (status == 'success') {
				$('section[role="main"]').html($(data).find('section[role="main"]').html());
			} else {
				// tell the user we could not connect to the server
			}
		});
	});
});

Първото нещо, което трябва да направим в нашата callback функция, е да проверим дали статусът на заявката ни е „success“. Ако е – значи всичко е наред и можем да преминем към обработването на съдържанието. В противен случай би било най-добре да изведем съобщение към потребителя, че сме имали проблем със заявката и да го подканим да опита пак. В този пример просто съм оставил коментар, който да напомни какво се очаква от нас да направим, но не съм предприел някакви действия.

Нека сега разясним какво става в реда от кода, който се изпълнява, ако заявката ни е върнала резултат. Първо селектираме елементът <section role="main"&ght; и променяме съдържанието на HTML-а вътре в него. Този HTML обаче взимаме отново чрез няколко действия. Първо взимаме съдържанието на data като jQuery обект, тъй като знаем, че това трябва да е просто маркъпа на една страница, но в променливата data той е просто текст. След като вече имаме страницата като jQuery обект можем да извикаме методът find() и да достъпим точно определен елемент от страницата. Това, от което се нуждаем е отново <section role="main">, чиито HTML взимаме. Така просто заменяме HTML-а на <section role="main"> от текущата страница с този на съответстващия елемент от страницата, която искаме да заредим.

Ще е добре все пак да обновим и навигацията, както и <title> тагът:

jQuery(function ($) {
	$('nav a').click(function (e) {
		e.preventDefault();
		
		$.get($(this).attr('href'), function (data, status, XHR) {
			if (status == 'success') {
				$('section[role="main"]').html($(data).find('section[role="main"]').html());
				
				$('title').html($(data).filter('title').html());
				$('header').html($(data).find('header').html());
			} else {
				// tell the user we could not connect to the server
			}
		});
	});
});

Обновяваме навигацията, като просто заменяме HTML-а на целия <header> подобно на <section>-а.

Забележете обаче, че за <title> използваме $(data).filter, вместо $(data).find(). Това е така, защото при заявки с jQuery всъщност получаваме масив с всички тагове от <head> и <body>. За section можем да използваме find, защото прилагаме методът за <div id="wrapper" class="shell">, но <title> се явява root елемент и за да го селектираме измежду останалите трябва да използваме filter, който връща само част от елементите, които отговарят на зададения CSS селектор.

Ако тестваме в момента ще видим, че всичко е наред при първия клик върху някой от линковете, но вторият път вече страницата се презарежда. Това е защото ние всъщност заменяме самите линкове, поради което вече нямаме назначени event handler-и към тях. Решенията са две: да назначаваме нови event handler-и всеки път, когато заредим нова страница или да променим начина, по който задаваме event handler-ите, така че да не се губят:

jQuery(function ($) {
	$(document).on('click', 'nav a', function (e) {
		e.preventDefault();
		...
	});
});

Вместо да селектираме първоначално линковете и да им задаваме click event handler, можем по този начин да зададем click event handler за целия документ, който остава неизменим, но зададем, че искаме функцията да се изпълни, само ако click event-а е бил генериран от елемент, отговарящ на дадения CSS селектор.

Можете да видите крайния резултат на http://magadanski.com/demo/ajax-requests/.

Проблем при този начин на зареждане на страници е, че адресът остава да сочи към първата отворена, така че ако натиснете F5 или се опитате да дадете линк към текущата страница на някой приятел ще останете разочаровани, че адресът всъщност сочи към началната страница и трябва да обяснявате къде трябва да се кликне, за да се види това същото. Този проблем се решава чрез deep linking, за който обаче ще говорим в друг урок.

  • […] седмица написах урок как можем да подобрим user experience-а на нашия сайт, като […]

  • Devseon каза:

    Чудесен урок! Това, което може да се направи по загатнатия проблем с url-а в address bar-а e да се променя през java script. Така ще се създаде илюзията, че наистина се отива на нова страница и адреса може да бъде копиран и използван. Примерно „#pagename“ зад url-а на текущата страница се постига с location.hash = „pagename“ и после при начално зареждане проверка
    if (location.hash == ‘#pagename’) //зареди желаната страница.

    Естествено по-интересно би било да се използва нововъведеният в HTML 5 метод
    history.pushState(data, „Title“, „url“); където може да се симулира напълно преминаване на друга страница без да се налага да се използва „#“ например. Може би все пак трябва да се спомене, че IE и pushState нямат никаква любов един към друг, но това няма значение, винаги може да се провери какъв браузър се използва и да се употреби да кажем hash в IE.

    • magadanski_uchen каза:

      Всичко с времето си. Следващият урок, който замислям е именно за deep linking, където ще разгледаме JS history API и pushState(), както и fallback към location.hash, като се уверим, че употребата му няма да презареди страницата (защото и това се среща в някои случаи).

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

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

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