In-Portal Developers Guide

This is a wiki-based Developers Guide for In-Portal Open Source CMS. The purpose of this guide is to provide advanced users, web developers and programmers with documentation on how to expand, customize and improve the functionality and the code the In-Portal software. Please consider contributing to our documentation writing effort.

K4:Работа с AJAX запросами

From In-Portal Developers Guide

Jump to: navigation, search
Работа с формами Работа с формами
Статьи в этой категории

AJAX (asynchronous JavaScript and XML) - подход к Web-программированию, позволяющий браузеру обмениваться информацией с сервером без перезагрузки страницы. AJAX не является самостоятельным языком программирования или технологией, это всего лишь концепция, объединяющая в себе использование нескольких языков и технологий.

Общая AJAX-модель заключается в следующем:

  • имеется web-страница, которая при помощи специального JavaScript-объекта посылает запрос на сервер. Запрос обычно содержит какие-либо данные, динамические полученные от клиента - например, значения заполненных на форме полей;
  • сервер получает запрос, обрабатывает его своими средствами, и возвращает браузеру результат. Для сервера полученный запрос ничем не отличается от запроса, который браузер посылает при загрузке любого URL. Соответственно, обработка также ничем не отличается;
  • результат, сгенерированный сервером, попадает в специальный JavaScript-объект, а также вызывается событие, которое инициирует обработку данного объекта. Чаще всего затем полученные данные используются для отображения каких-либо изменений на странице с помощью технологии DHTML (изменения исходного HTML с помощью JavaScript'a).

Contents

Описание AJAX подхода (в K4)

В течение последних нескольких лет AJAX стал активно использоваться, поскольку появилось достаточное количество задач, где данный подход является оптимальным. Для этого был написан специальный JavaScript-класс - Request. Данная статья описывает работу с AJAX при помощи данного класса. Также использование этого класса настоятельно рекомендуется при любой работе с AJAX, т.к. в нём уже учтены многие вещи, на самостоятельное решение которых может быть потрачено значительное время.

Для того, чтобы использовать класс Request на Front-End, в шаблоне должен быть подключен файл ajax.js. В административной консоли этот файл подключается автоматически в шаблоне "header.tpl". Чтобы сделать простой запрос, нужно использовать метод makeRequest класса Request:

var p_busyReq = false;
Request.makeRequest(p_url, p_busyReq, p_progId, p_successCallBack, p_errorCallBack, p_pass, p_object);

Данный метод принимает следующие параметры:

название описание
p_url (string) URL-адрес, к которому производится запрос.
p_busyReq (bool) Индикатор "занятости" AJAX объекта. При значении true запрос не выполнится.
Image:Tipbox Icon.gif Следует иметь в виду, что его значение изменяется внутри метода.
p_progId (string) ID HTML-элемента (обычно это div элемент), в котором показывать процесс загрузки данных через AJAX. После успешного процесса загрузки данных в этот элемент автоматически будет помещён весь полученный результат.
p_successCallBack (function) Функция обратного вызова (callback), которая будет вызвана при успешном завершении запроса.
p_errorCallBack (function) Функция обратного вызова (callback), которая будет вызвана в случае ошибки.
p_pass (mixed) Параметр(-ы), который может использоваться для передачи дополнительной информации, нужной для обработки полученного AJAX-ответа.
Image:Tipbox Icon.gif Данный параметр будет передаваться во все функции обратного вызова.
p_object (object) Если функция обратного вызова является методом объекта, то нужно передавать этот объект в качестве данного параметра.
Image:Tipbox Icon.gif Данный параметр будет передаваться во все функции обратного вызова.

При получении ответа (успешного или нет) вызывается соответствующая функция обратного вызова. При каждом вызове в неё передаётся 3 параметра:

название описание
req (object) Объект, содержащий в себе результат AJAX-запроса.
p_pass (mixed) Значение из аналогичного параметра, переданного в метод makeRequest.
p_object (object) Значение из аналогичного параметра, переданного в метод makeRequest.

Ниже приведены примеры написания функций обратного вызова, передаваемых методу makeRequest:

function successCallBack (req, p_pass, p_object) {
	var result_text = req.responseText; // полученный текст
	var result_xml = req.responseXML; // полученный XML
	// работа с результатом запроса
}
 
function errorCallBack (req, p_pass, p_object)
{
	var error_code = req.status;
	// обработка ошибки
}

XML-объект в AJAX-ответе доступен только в случае, когда в ответе был послан соответствующий "Content-Type" заголовок.

Использование

На форме имеется элемент ограниченного выбора (dropdown), использующийся для выбора пользователя системы (например, если это форма ввода какого-либо документа, то это может быть пользователь, который должен будет этот документ обработать). В элементе отображается имя, фамилия и адрес электронной почты, однако нужно видеть все данные выбранного пользователя, чтобы быть уверенным, что выбран правильной человек.

Поиск решения

  • Можно значения всех полей пользователя попробовать вывести в dropdown, но удобство такого решения оставляет желать лучшего.
  • Можно попробовать загрузить все данные в JavaScript массив и показывать соответствующие значения в read-only полях при смене выбранного значения. Этот вариант намного лучше предыдущего, однако он (как в принципе и предыдущий) обладает существенным недостатком - загружается большое количество данных, большинство из которых, скорее всего, использовано не будет, но тем не менее, при большом количестве данных может повлиять на время загрузки страницы и на работоспособность браузера.
  • Такой же вариант, как и предыдущий, но с одним отличием: значения полей конкретного пользователя будут загружаться только при смене значения, выбранного в dropdown. Для этой цели отлично подойдёт AJAX. Ввиду отсутствия недостатков предыдущих вариантов данный подход является оптимальным.

Решение

В первую очередь нужно создать объект для работы с данными пользователя:

function UserInfoManager() {
	this.userSelect = document.getElementById('<inp2:document_InputName field="ResponsibleUserId" />');
	this.busy = false;
 
	addEvent(this.userSelect, 'change', this.getUserInfo);
}

Хотя, в принципе, создание объекта не является необходимым, но это является более правильным подходом и настоятельно рекомендуется при выполнении задач любого уровня сложности. Также в данном случае создаётся именно объект, а не класс, поскольку здесь он требуется для выполнения одной единственной конкретной задачи. В случае, если схожая функциональность требуется для решения нескольких задач, рекомендуется создавать класс и работать в каждом случае с его экземплярами.

Для упрощения работы добавлено свойство userSelect, а также выполнение метода getUserInfo назначено на событие change соответствующему объекту. Затем требуется добавить метод, который будет осуществлять запрос:

UserInfoManager.getUserInfo = function() {
	var user_id = this.userSelect.value;
	if (user_id) {
		var url = this.getRequestUrl(user_id);
		Request.makeRequest(url, this.busy, null, this.receiveUserInfo, this.processRequestError, '', this);
	}
}
 
UserInfoManager.getRequestUrl = function(user_id) {
	var url = '<inp2:m_Link pass="all,document" doc_event="OnGetUserInfo" UserId="#USER_ID#" />';
	return url.replace('#USER_ID#', user_id);
}

В методе getUserInfo в качестве обработчиков ответа установлены методы receiveUserInfo и processRequestError, а также передан текущий объект. Также добавлен метод getRequestUrl, который подменяет шаблон #USER_ID# на ID пользователя, для которого производиться запрос.

В классе DocumentEventHandler, который является обработчиком событий для префикса "document" нужно добавить метод для формирования и отсылки ответа с сервера:

function OnGetUserInfo(&$event)
{
	// отсылка заголовка с Content-Type text/xml
	$this->Application->XMLHeader('1.0');
 
	$user =& $this->Application->recallObject('u.-getinfo', 'u', Array ('skip_autoload' => true));
	/* @var $user UsersItem */
 
	$user_id = $this->Application->GetVar('UserId');
	$user->Load($user_id);
 
	$result = '';
	foreach ($user->Fields as $field_name => $field_options) {
		$result .= '<field name="' . $field_name . '">' . $user->GetField($field_name) . '</field>';
	}
 
	echo '<fields>' . $result . '</fields>';
	$event->status = erSTOP;
}

Следует обратить внимание на то, что результат выводится с помощью функции echo и то, что событию присваивается статус erSTOP. Данный статус был специально разработан для событий, вызываемых с помощью AJAX'a и означает, что скрипт после выполнения данного события прекратит работу, а также, в случае включенного режима отладки, предотвратит вывод сообщений отладчика (что могло бы "поломать" XML или привести к появлению ненужных данных в результате запроса).

Таким образом, браузеру вернётся примерно следующий XML:

<?xml version="1.0" encoding="utf-8"?>
<fields>
	<field name="PortalUserId">1</field>
	<field name="Login">intechnic</field>
	<field name="Email">sergeyg@intechnic.lv</field>
	<field name="FirstName">Сергей</field>
	<field name="LastName">Гриб</field>
	<field name="Phone">12345678</field>
	<field name="Fax">12345679</field>
	...
</fields>

Теперь требуются обработка возвращённых данных. Для этого у используемого JavaScript объекта пишется соответствующий метод:

UserInfoManager.receiveUserInfo = function(req, p_pass, p_object) {
	var user_info_xml = req.responseXML;
	var fields_arr = user_info_xml.getElementsByTagName('FIELD');
 
	for(var i = 0; i < fields_arr.length; i++) {
		var field_name = fields_arr[i].getAttribute('name');
		var control = p_object.getControl('User' + field_name);
		if (control) {
			control.innerHTML = fields_arr[i].innerText;
		}
	}
}
 
UserInfoManager.getControl = function(field_name) {
	var field_mask = '<inp2:document_InputName field="#FIELD#" />';
	return document.getElementById( field_mask.replace('#FIELD#', field_name) );
}

Следует обратить внимание на то, что обращение к объекту UserInfoManager внутри функции обратного вызова идёт через объект, переданный в параметре p_object. Это требуется из-за того, что с момента назначения метода объекта в качестве функции обратного вызова в нём уже не возможно использовать ключевое слово "this".

Описание AJAX подхода (в принципе)

Для работы с AJAX-запросами используется объект XMLHttpRequest. В Internet Explorer'e его можно получить как ActiveX-объект, в остальных браузерах (Firefox, Opera и других) он является встроенным JavaScript объектом. Поэтому для получения объекта обычно используется функция, внутри которой объект получается в зависимости от браузера, например:

function getXMLHttpObject() {
	try {
  		// Firefox, Opera 8.0+, Safari
		return new XMLHttpRequest();
	}
	catch (e) {
		// Internet Explorer
		try {
			return new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (e) {
			try {
				return new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch (e) {
				alert("Your browser does not support AJAX!");
				return false;
			}
		}
	}
}

Для того чтобы послать запрос, используются методы open и send:

httpRequest = new getXMLHttpObject();
httpRequest.open('GET', 'http://www.intechnic.lv/get_result.php?some_param=some_value', true);
httpRequest.send(null);

Методу open передаются 3 параметра:

  • Метод протокола HTTP, который будет использован для запроса, обычно это GET.
  • URL, на который запрос будет послан.
  • Указывает на то, что запрос должен выполняться синхронно со страницей, его вызвавшей.

Третий параметр является ключевым при использовании AJAX'a - при значении true выполнение Javascript'a продолжится сразу после посылки запроса, не дожидаясь ответа, другими словами, посылается асинхронный запрос. При значении false выполнение кода не будет продолжено до тех пор, пока не будет получен ответ от сервера, т.е. будет отослан синхронный запрос.

Для того, чтобы узнать текущий статус выполнения запроса, используется свойство readyState объекта httpRequest. Его значения расшифровываются следующим образом:

  • 0 - не загружался;
  • 1 - загружается;
  • 2 - загрузился;
  • 3 - обмен данными;
  • 4 - готово.

Чтобы узнать результат выполнения AJAX-запроса, нужно проверить значение свойства status. В нём хранится стандартный HTTP response code (например, 404 - page not found, 403 - forbidden, 500 - internal server error и другие). А чтобы "поймать" момент, когда статус будет изменён, имеется свойство onreadystatechange, в которое нужно присвоить функцию обратного вызова, которая будет вызвана в момент изменения статуса:

httpRequest.onreadystatechange = function() {
	try {
		if (httpRequest.readyState == 4) {
			if (httpRequest.status == 200) {
				alert(httpRequest.responseText);
			} else {
				alert('Request failed: error ' + httpRequest.status + ' encountered!');
			}
		}
	}
}

Как видно из этого запроса, для получения текста результата используется свойство responseText. Таким образом, на экран выведется текст "SOME_VALUE", если файл get_result.php будет содержать следующий код:

<?php
	echo strtoupper($_GET['some_param']);