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&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

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

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

XML?

Каква е ролята на 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) {
			
		});
	});
});

GET

В event handler функцията добавяме $.get. По принцип браузърите зареждат нова страница с GET заявка. Тази функция ни позволява да заредим съдържание от даден URL.

url

Този URL се подава като първи аргумент на функцията. В нашия случай това е просто href атрибута на съответния линк.

Обърнете внимание на елегантността, с която работим.

Първо — нужно е URL-ът, от който ще зареждаме информацията да не е въведен в самия JavaScript. Той трябва да е независим от markup-а, към който се прилага.

Второ — взимаме този URL от href атрибута на линка. Можехме да въведем това и като data атрибут, но в случая е уместно да използваме href. Ако потребителят случайно е изключил JavaScript-а си кликайки на линка просто ще зареди другата страница. Няма да има AJAX, но няма да има и грешки. Правим го за достъпност.

Допълнителни аргументи

Вторият аргумент на $.get по принцип е обект с допълнителни аргументи, които бихме искали да изпратим с нашата GET заявка. В случая нямаме нужда от това и подаваме направо третия аргумент.

Callback функция

Той е функция, която да се изпълни при получаване на резултат от заявката. Нужно е да направим това във функция поради асинхронността на кода.

Заявката отнема време да получи своя отговор. Първо трябва да достигне до сървъра. След това сървърът трябва да я обработи и да подготви резултат. Накрая трябва и този резултат да стигне обратно до нас. Не искаме страницата ни да замръзне през това време. То може да е само 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 функцията

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

Нека сега разясним какво става в реда от кода, който се изпълнява, ако заявката ни е върнала резултат.

Замяна на старото съдържание с ново

Първо селектираме елементът <section role="main"> и променяме съдържанието на 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>-а.

filter и find

Забележете обаче, че за <title> използваме $(data).filter, вместо $(data).find().

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

Ако тестваме в момента ще видим, че всичко е наред при първия клик върху някой от линковете. При втория обаче вече страницата се презарежда.

Това е защото ние всъщност заменяме самите линкове. По тази причина вече нямаме назначени event handler-и към тях. Решенията са две:

  1. да назначаваме нови event handler-и всеки път, когато заредим нова страница
  2. да променим начина, по който задаваме event handler-ите, така че да не се губят:
jQuery(function ($) {
	$(document).on('click', 'nav a', function (e) {
		e.preventDefault();
		
		// ...
	});
});

.click и .on('click')

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

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

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

3 Отговори на “AJAX Заявки”

  1. Чудесен урок! Това, което може да се направи по загатнатия проблем с 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.

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

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

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

Този сайт използва Akismet за намаляване на спама. Научете как се обработват данните ви за коментари.