Joomla 1.5: Использование Ajax

Joomla 1.5: Использование Ajax

Индекс материала
Joomla 1.5: Использование Ajax
Обработка на стороне сервера.
Обработка на стороне клиента.
Обходим политику единого источника
Все страницы

В данной заметке вы узнаете, о том как разработать простое Ajax взаимодействие с использованием MooTools в Joomla! 1.5. Будет показан пример, для правильной обработки последовательности ответов сервера, даже тогда, когда она не совпадает с последовательностью запросов. Так же будет рассмотрена проблема "политики единого источника" браузеров, т.е. возможность работы Ajax кроссдоменно с использованием метода POST.

В Joomla 1.5 в качестве JavaScript framework - а используется ... увы MooTools. Он, конечно включает в себя поддержку Ajax и делает построение Ajax запросов намного проще (кто бы мог подумать) , хотя Есть еще некоторые недоработки. Возможно, наиболее важным преимуществом использования framework - а MooTools для Ajax является то, что вы получаете полностью кросс-браузерные и переносимые решения, и ваш код будет работать во всех браузерах, а это весомый аргумент.

Лично я совершенно не в восторге от этого решения разработчиков, но раз уж они не собираются отказываться от него, то придётся нам разбираться с MooTools, по крайней мере, с тем, как он реализует Ajax транспорт.

Данная статья относится к Joomla! 1,5 потому как Joomla! 1,6 поставляется с более поздней версией MooTools, которая имеет другой способ реализации Ajax запросов.

Клиентский Ajax код, использующий MooTools

В типичном приложении Ajax мы как правило получаем некие данные, которые посылает наш собственный сервер, или какой либо другой, удаленный веб-сервис, после того, как мы получим ответ возможно нам придётся обновить часть страницы с учётом полученных данных. В типичной реализации Ajax есть три действующих "лица":

  • HTML-элемент, изменение состояния которого инициализирует Ajax - запрос.
  • HTML-элемент, состояние которого может измениться по пришествии ответа на Ajax - запрос.
  • Ну и собственно сам Ajax JavaScript-код.

Начинём с первого из них: необходимо определить элемент на странице, который будет инициировать Ajax - запрос. А ещё зададим этому элементу уникальный идентификатор id. Например, у вас есть выпадающий список на странице, и вы хотите делать Ajax - запрос всякий раз, когда пользователь, что то выбирает из этого списка. В общем нужно получить выпадающий список типа этого:

<select name="drop-down" id="drop-down">
        <option value="1">Item 1</option>
        <option value="2">Item 2</option>
        <option value="3">Item 3</option>
</select>

Этот список вполне себе ничего - генерируется следующим кодом:

<?php

$options = array();

$options[] = JHTML::_( 'select.option', '1', 'Item 1' );
$options[] = JHTML::_( 'select.option', '2', 'Item 2' );
$options[] = JHTML::_( 'select.option', '3', 'Item 3' );

echo JHTML::_( 'select.genericlist', $options, 'drop-down' );

Второе: нам нужен HTML-элемент, который будет содержать результат вызова Ajax. Это может быть какой - нибудь DIV, который так же должен иметь уникальный идентификатор id, например так:

<div id="ajax-container"></div>

И третье: теперь, нужно подключить код JavaScript, который сделает запрос Ajax, и выведет ответ на экран. Обычно не приходится беспокоиться о подгрузке MooTools, так как Joomla делает это для Вас автоматически , но иногда нужно сделать это вручную, добавив следующий код:

<?php
JHTML::_( 'behavior.mootools' );

Есть много способов, добавить JavaScript код в Joomla. Один из способов, который позволяет избежать проблем, заключается в использовании PHP "Heredoc" синтаксиса следующим образом:

<?php
$ajax = <<<EOD
... Здесь ваш Javascript - код ...
EOD;

// Включаем скрипт в работу:
$doc = & JFactory::getDocument();
$doc->addScriptDeclaration( $ajax );

Ещё в код JavaScript нужно добавить обработчик событий для элементов, которые будут инициировать вызовы Ajax. В MooTools это делается, используя следующий вызов ( в нашем случае - это выпадающий список с id="drop-down" ) :

window.addEvent( 'domready', function() {
 
	$('drop-down').addEvent( 'change', <function-declaration> );
 
});

где <function-declaration> это код JavaScript, который должен быть вызван, когда происходит событие 'change' . Обратите внимание, что нужно всегда задержать вызов addEvent пока DOM дерево страницы не загрузится полностью. В MooTools это делается с помощью метода window.addEvent - таким макаром мы "вешаем" обработчик на событие domready.

Не обязателно использовать событие OnChange для запуска механизма Ajax, например, можно ещё использовать событие OnClick как инициализатор. , должен будет создать экземпляра MooTools класса: Ajax, выглядящий примерно так:

var a = new Ajax( {$url}, {
	method: 'get',
	update: $('ajax-container')
}).request();

где {$url} является PHP переменной, содержащей URL для Ajax - запроса. Свойство update - было использовано, чтобы скопировать ответ сервера в HTML - элемент с id="ajax-container". Это быстро и удобно, но очень часто вам нужно будет обрабатывать ответ каким-либо образом, прежде чем показывать его пользователю. Как правило, ответ приходит в JSON - формате, и вы должны декодировать и отформатировать его соответствующим образом, перед отображением в HTML - элементе - контейнере. Чтобы сделать это, используйте свойство OnComplete объекта Ajax, а не свойство update.

var a = new Ajax( {$url}, {
	method: 'get',
	onComplete: <completion-function>
}).request();

Здесь <completion-function> - это функция JavaScript, которая будет вызвана, когда получен ответ удаленного сервера.Эта функция может содержать функционал для обработки данных перед выводом их в HTML - элемент - контейнер. Ниже приводится более полный пример функции Ajax, которая получает данные с сервера в формате JSON, декодирует его, а затем помещает данные ответа в HTML - элемент.

window.addEvent( 'domready', function() {
 
	$('drop-down').addEvent( 'change', function() {
 
		$('ajax-container').empty().addClass('ajax-loading');
 
		var a = new Ajax( {$url}, {
			method: 'get',
			onComplete: function( response ) {
				var resp = Json.evaluate( response );
 
				// ... какой то код, который нужно выполнить, когда пришёл
				// ответ сервера...
 
				$('ajax-container').removeClass('ajax-loading').setHTML( output );
 
			}
		}).request();
	});
});

Обратите внимание, что в этом примере есть еще кое-какой код, что бы добавить, а затем удалить CSS класс 'ajax-loading' для HTML - элемента Ajax-контейнера. Как правило, добавление этого класса приведет к отображению прелоадера - графического элемента, который будет загружен в качестве фонового изображения, чтобы пользователь не расслаблялся, а понимал, что система все еще работает.


Обработка Ajax - запросов на стороне сервера.

Серверная часть вашей реализации Ajax может быть уже каким - либо готовым веб-сервисом, в этом случае она уже написана за вас. Но, что делать, если вам нужно написать код сервера самостоятельно, то имеет смысл взять за основу Framework Joomla. Хотя можно реализовать серверную сторону и без Joomla, но в таком случае вам будет не хватать целого ряда важных функций, которые делают написание безопасного серверного Ajax - кода очень простым. ( Это, блин, не моё - это перевод!)

Как правило ответ сервера является XML или JSON - форматом. В Joomla 1.5 которая поддерживает MVC это делается особенно легко, сука, и не принуждённо...Просто добавьте новый класс "вида" в директорию view, в файл с именем view.xml.php, или view.json.php в зависимости от требуемого формата. Иногда ответ, необходимый для конкретного запроса настолько прост, что создание нового представления было бы излишним. В этом случае нет никакой необходимости создавать файл и класс "вида" - всё можно запхать в контроллер.

Генерация JSON ответа

PHP имеет встроенные функции для кодирования и декодирования JSON данных. Вы можете кодировать данные, используя json_encode функции, например:

<?php
// Получаем данные для ответа:
$data = array( 'some data' );
 
// Отсылаем "клиенту" данные в JSON - формате:
echo json_encode( $data );

Функция Json_encode может кодировать почти все типы данных, такие как строки, массивы и объекты, хотя, нужно иметь в виду, что json_decode функция будет возвращать только объект (и, возможно, ассоциативный массив). Это есть - правильно! Тем более, что при таком раскладе удобно устанавливать правильный MIME-тип для ответа. В некоторых приложениях Вы могли бы также изменить предложенное имя файла на что-то другое чем "index.php", который Вы, вероятно, получите по умолчанию. В следующем примере предложенное имя файла изменено на имя представления с добавленным расширением ".json".

<?php
// Получаем данные для ответа:
$data = array( 'some data' );
 
// Получаем document объект.
$document =& JFactory::getDocument();
 
// Устанавливаем MIME тип для JSON ответа.
$document->setMimeEncoding( 'application/json' );
 
// Подменяем имя файла.
JResponse::setHeader( 'Content-Disposition', 'attachment; filename="'.$view->getName().'.json"' );
 
// Отсылаем "клиенту" данные в JSON - формате:
echo json_encode( $data );

Генерация XML ответа

Joomla поддерживает простой и достаточно эффективный класс, JSimpleXML , который может быть использован для создания XML-вывода, например, для Ajax. Однако, объектно-ориентированный XML генератор хренОв тем, что он медленный и жрётъ память, хоть JSimpleXML и представляет собой облегченную реализацию. Например, следующий код прекрасно обходится и без JSimpleXML и будет выводить XML-документ, состоящий из корневого элемента, <root> содержащего элементы <items> , которые сами содержат один или несколько элементов <item> с данными:

<?php
$document =& JFactory::getDocument();
$document->setMimeEncoding( 'text/xml' );
 
// Выводим XML декларацию.
echo '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
 
// Выводим корневой - root элемент.
echo '<root>' . "\n";
 
// Выводим данные.
echo "\t" . '<items>' . "\n";
if (!empty( $data )) {
	foreach ($data as $datum) {
		echo "\t\t" . '<item>' . "\n";
		foreach ($datum as $key => $value) {
			echo "\t\t\t" . '<' . $key . '>' . htmlspecialchars( $value ) . '</' . $key . '>' . "\n";
				}
		echo "\t\t" . '</item>' . "\n";
	}
}
echo "\t" . '</items>' . "\n";
 
// Закрываем корневой - root элемент.
echo '</root>' . "\n";

Обратите внимание, что данные должны быть экранированы с помощью htmlspecialchars, чтобы обезопасить HTML символы. Также, в этом примере была предпринята попытка привести "вид для печати" в божеский вид путём применения табов и возвратов каретки. Это не является обязательным, и эти дополнительные символы могут быть удалены.


Правильная обработка асинхронных Ajax - ответов в MooTools

Легко забыть, что начальная буква "А" в слове "Ajax" обозначает "асинхронный", что означает, что, как только пользователь инициировал запрос Ajax, он так же имеет право делать другие вещи c интерфейсом, в том числе совершать и ...другие Ajax - запросы.

Как создатель веб-приложений вы должны принять во внимание, что ответы на эти запросы могут поступать обратно совершенно не в том же порядке, в котором они были сделаны. Если игнорировать это недоразумение, оно может иметь печальные последствия для пользователей.

Например, в типичной реализации Ajax, где пользователь выбирает страну из выпадающего списка стран, а затем представлен раскрывающийся список регионов внутри этой страны, вызов Ajax производится всякий раз, когда изменилась страна. Ответом на этот запрос будет список регионов выбранной страны. Но предположим, что пользователь несколько раз быстро делает выбор, что довольно просто сделать с помощью клавиатуры.

Допустим пользователь нажимает клавишу "U" и первой страной, которая отобразится в списке - будет "Uganda". Это не то, что хочет пользователь, и он нажимает клавишу "U" ещё несколько раз, пока он получает "United Kingdom", затем еще раз, чтобы в итоге получить то, что он действительно хотел: "United States". Эти действия вызывают целый ряд последовательностей Ajax вызовов, с последним из которых является выбор "United States".

Предположим, что ответ на запрос "United Kingdom" по некоторым причинам задерживается. Это может быть из-за нагрузки на сервер, или это могут быть задержки в сети. Но по какой то причине, он прибыл после ответа на запрос "United States". В итоге пользователю будет предложен список регионов из "United Kingdom", хотя его выбор был "United States". Такое поведение явно нежелательно.

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

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

Но должен быть какой-то способ определить, является ли конкретный запрос выполненным или нет. Для этого вы можете расширить MooTools Ajax "класс" следующим образом:

// Расширяем MooTools Ajax "класс" :
Ajax = Ajax.extend({
 
    initialize: function( url, options ) {
        this.parent( url, options );
        this.ready = false;
        this.output = '';
    },
 
    onComplete: function() {
    	this.ready = true;
    	this.parent();
    }
 
});

Это добавит два дополнительных свойства к "классу" Ajax:

  • ready - это логический флаг - доступен ли ответ на запрос или нет
  • output - контейнер для хранения ответа.

Теперь нужно создать механизм, реализующий очередь для хранения запросов. Каждый новый запрос будет помещён в очередь а завершённые запросы будут извлекается из очереди. Фокус в том, что если более новый запрос завершается быстрее чем более старые, то этот ( более новый ) запрос остается в очереди, а не удаляется из последовательности. Очередь это простой массив, объявленный в глобальной области:

var AJAX_QUEUE = [];

Код window.addEvent модифицирован таким образом, что объект Ajax помещается в очередь перед запросом:

window.addEvent( 'domready', function() {
 
	$('drop-down').addEvent( 'change', function() {
 
		$( 'ajax-container' ).empty().addClass( 'ajax-loading' );
		var url = 'index.php?option=com_component&args=whatever';
		var a = new Ajax( url, {
			method: 'get',
			onComplete: function( response ){
 
			// Здесь код функции Ajax onComplete...
 
			}
		});
		AJAX_QUEUE.push( a );
		a.request();
	});
});

Затем нужно добавить некоторый дополнительный код к функции onComplete, которая вытолкнет завершенные запросы из очереди, но только в правильном порядке:

// Сохраняем ответ в Ajax объект.
this.output = response;
 
// Обработка очереди:
while ( AJAX_QUEUE.length ) {
 
	// Если старший запрос в очереди не обработан - ничего не делаем:
	if ( !AJAX_QUEUE[0].ready ) break;
 
	// Выталкиваем запрос из очереди:
	r = AJAX_QUEUE.shift();
 
	// Выводим данные только когда очередь пуста:
	if ( !AJAX_QUEUE.length ) {
		$('ajax-container').removeClass('ajax-loading').setHTML(r.output);
	} 
}

Обратите внимание, что результат выводится только тогда, когда очередь, наконец, пуста, поэтому пользователь никогда не увидит результатов промежуточных запросов. Если у вас есть несколько полей, посылающих Ajax - запросы на одной странице, то вы должны обеспечить каждое своей очередью, но по существу принцип остается тем же.


Как обойти политику единого источника веб-браузера в приложениях Ajax

Если Вы должны получить Ajax - доступ к веб-сервису, который не находится в Вашем домене, то Вы столкнетесь с проблемой: браузерной "политикой единого источника", которая из соображений безопасности, будет препятствовать этому. Есть несколько способов обойти это недоразумение, конечно, если вы доверяете этому веб-сервису.

Наиболее распространенный метод - это получить доступ к удаленному веб-сервису через прокси, работающий в Вашем домене. Этот метод обычно достаточно прост и позволяет обмениваться запросами методом POST, но может стать более геморройным, если удаленный сервер требует аутентификации или использует cookie, чтобы отследить состояние между запросами ( Но всё решаемо - PHP CURL форева! ) . Но как плюс, использование этотго метода обычно облегчает кэширование Ajax - ответов, что обычно является довольно трудной задачей.

Чтобы добавить простой прокси к Вашему Joomla - компоненту , Вы просто должны добавить дополнительную задачу к контроллеру. Допустим у нас есть некий хелпер - статический клиентский класс httpClient с методом call, который запросит страницу у удаленного сервера. Тогда вот пример реализации прокси:

<?php
/**
 * Поддержка кросс - доменных Ajax - запросов используя компонент "прокси".
 */
function proxy()
{
  /*
   * Переписываем строку запроса
   * с учётом требований API реального сервиса:
   */
	$uri = & JFactory::getURI();
	$query = $uri->getQuery( true );
	$query['option'] = $query['type'];
	unset( $query['type']);
	unset( $query['task']);
	if (isset( $query['request'] )) {
		$query['task'] = $query['request'];
		unset( $query['request']);
	}
 
	// Делаем запрос к API реального сервиса
	$response = httpClient::call( $query );
	if ($response->status != '200') {
		JError::raiseError( 500, JText::_( 'Remote server error' ) );
		return false;
	}
 
	// Возвращаем ответ клиенту.
	echo $response->data;
	jexit();
}

В строках 11 - 19 пример показывает, как обойти ситуацию, когда удаленный сервер также работает на Joomla, в этом случае он будет требовать URL содержащие option и task - GET переменных, которые могут не соответствовать тем, которые требуются для обращения к вашему прокси. Это нужно для того, если вы хотите сделать Ajax - запрос к удаленному серверу, используя этот адрес:

http://www.remoteserver.com/index.php?option=com_remotecomponent&task=remotetask&arg=something

Но вам нужно сначала отправить запрос на собственный прокси, расположенный по этому URL:

http://www.localserver.com/index.php?option=com_localcomponent&task=proxy&type=com_remotecomponent&request=remotetask&arg=something

Вам нужно будет адаптировать путь примерно, как показано выше.

Пока всё. Оригинал статьи

Добавить комментарий


Защитный код
Обновить






Кто на сайте
Сейчас 53 гостей онлайн