AJAX, как много в этом слове. Давно хотел написать про AJAX и битрикс, немного критики, пара предложений, разымшления на тему. Более-менее продвинутые разработчики под битрикс знают как работает AJAX в битриксе, регулярно сталкиваются с CAJAX и прочими приблудами. Многих наверное устраивает.[spoiler]
Моя история "AJAX и битрикс" началась больше двух лет назад. Тогда у битрикса AJAX'а еще не было, а мне его очень хотелось, я написал простейшую обработку, которую до недавнего времени использовал. Надо отметить, использовалась она исключительно для компонентов и работала на Prototype. Зачем в битриксе AJAX для страниц для меня пока загадка. Алгоритм был примерно следующий: 1. Ссылка на странице формирует AJAX-запрос к файлу /ajax.php 2. /ajax.php, анализируя параметры запроса, подключает и показывает указанный в параметрах компонент с указанным шаблоном и параметром. 3. Скрипт на странице получает ответ от /ajax.php и вставляет в указанный контейнер.
С этой приблудой было несколько проблем: 1. Все параметры указываются в запросе. Таким образом, пользователь может видеть название компонента и шаблона, идентификаторы инфоблоков и разделов, настройки компонентов. Используя это, он может передавать произвольные данные и по сути получать доступ ко всем инфоблокам, для которых у него есть права на чтение хотя бы. Ну а все мы знаем, что далеко не на каждом проекте админы настраивают корректно права в инфоблоках. Могли быть проблемы. А для отображения тех или иных свойств права вообще не настраиваются. 2. Достаточно сложно передавать все параметры в запрос - это очень большой список, да и вообще строка запроса получается не эстетичной. 3. Сложности с генерацией ссылок на странице компонента - это ж надо знать компонент, его шаблон, его параметры, всё это оформить в виде ссылки с корректным синтаксисом, вставить куда надо.
Потом появился стандартный битриксовский AJAX и его реализация. С тех пор он особо не менялся. В поставке битрикса есть все необходимые js-скрипты, класс CAJAX для работы в PHP и документация. Кстати, документация че-то исчезла в последнее время по AJAX, на старом месте ее нет. А в обычной документации никогда и не было. Честно говоря, со стандартным битриксовским AJAX-ом я просто не разобрался, как он работает, ну просто у меня голова не соображала на тот момент, поэтому по-прежнему использовал свою реализацию.
По мере эволюции своих разработок я перелез с Prototype на jQuery. Это был довольно поздний, но важный переход. В jQuery отлично реализован функционал селекторов и AJAX, а создаваемый код очень красивый и компактный. На сколько может судить человек, который плохо вообще знаком с JS. Например, очень мне нравится, как вешаются события на объекты.
Этот код позволяет повесить событие сразу на все ссылки с классом "link", будет показываться предупреждение с текстом аттрибута href. Красота, грех не использовать. Красиво, компактно, удобно. Это просто отстраненный пример, забыли.
Далее я расскажу, как выглядит сегодня мой обработчик AJAX в последней реинкарнации. Чтобы было понятней, разделю на несколько частей
1. Вызов AJAX на страницах компонентов AJAX вызывается на всех ссылках, у которых есть класс "ajax_link". Привязка делается следующим образом:
Данный код загрузит данные через AJAX в <div id="cart-form"></div>. Надо отметить, что эти же параметры можно вешать на абсолютно любой html-объект, не только a, но еще и на img, span, div, form и т.д. Да, нарушается синтаксис HTML, но кого это волнует?
Подведу итог первой части. Сделан простой и понятный синтаксис формирования ссылок на AJAX. О AJAX_CALL_ID, ajax_load() и прочем далее.
2. Обработка вызовов AJAX Допустим, пользователь нажал на ссылку, описанную выше, что происходит далее. Так как ссылка имеет класс "ajax_link", то она поступает в соответствующий обработчик JS, который был описан выше для ссылок с этим классом, в нем вызывается функция ajax_load(CONTAINER, AJAX_CALL_ID, PARAMS), где параметры те же, что в ссылке. По сути то же самое. Если взять приведенную выше в пример ссылку, то всё можно было бы сделать аналогично, но описать событие click внутри ссылки:
Теперь приведу листинг собственно функции ajax_load
function ajax_load (container, ajax_call, params) {
if (!$.isArray(params)) {
params = $.queryString(params);
}
$.post(
'/bitrix/tools/ajax.php?ajax_call='+ajax_call,
params,
function (data) {
if (container) {
$(container).html(data);
}
}
);
}
Проясняю, как работает, хотя вроде тут всё понятно. $.queryString(params); - эта функция из дополнительного модуля jQuery, который называется "URL Utils". В данном случае используется для преобразования строки запроса в массив значений, если в функцию переданы параметры вызова не как массив. Кстати, модуль очень полезный, рекомендую. Да, кстати, в функцию ajax_load третий параметр можно передавать и как массив и как строку вида param1=val1¶m2=val2... Это как раз используется в ссылка с классом ajax_link - там так удобней. А вот если, допустим, нам надо отправить через AJAX данные формы, то здесь мы можем использовать как раз массив, полученный с помощью $.serializeArray(). Далее по коду происходит собственно отправка AJAX-запроса методом POST - функция $.post. Почему именно метод POST? Просто POST не ограничен по объему отправляемых данных, а запрос GET ограничен. Хотя это условность и зависит от браузера, веб-сервера и его настроек, общепринято, что длина запроса HTTP GET должна быть до 4 кб. И опять же это не эстетично. Многие наверное видели эти жуткие огромные запросы с кучей непонятных параметров. Еще с GET могут быть проблемы с кодировкой, но этот вопрос я подробно не изучал.
А запрос отправляется в файл /bitrix/tools/ajax.php - сюда он переехал из корня, чтобы там не мешался. К нему же подключается AJAX_CALL_ID, передаются параметры и обрабатывается ответ сервера. Что делать с этим ответом в контейнере - уже не важно. Этот контейнер может быть в самом компоненте, может быть где-то на странице, может быть скрытой областью и использоваться еще как-то. Главное, что в него передался ответ.
3. Страшная тайна AJAX_CALL_ID Я стремлюсь к эстетике. А как я писал ранее, в моем первом обработчике AJAX все параметры писались прямо в ссылке, что было некрасиво. AJAX_CALL_ID используется для маскировки всех параметров AJAX-запроса. Он формируется в компоненте из названия, шаблона и параметров с помощью простенькой функции:
$AJAX_CALL_ID = CBexxShop::ajaxId();
Это просто md5-хэш. Ниже привожу листинг функции CBexxShop::ajaxId(), чтобы было понятно как оно работает:
Как видите, в функцию можно передать любые параметры, тем самым сформировать AJAX_CALL_ID для любого компонента, а не только для текущего. Для большей безопасности в хэш добавляется параметр из системных настроек server_uniq_id. Далее по сформированному хэшу мы сохраняем все данные о компоненте в $_SESSION['ajax_components'][$ajax_call_id]. Затем эти данные можно получить по этому хэшу, ведь хэш передается во всех запросах AJAX, таким образом мы скрыли все параметры вызываемого компонента и для простого смертного не получится вызывать любой компонент с любым шаблоном и любыми параметрами. Я надеюсь. Сформированный AJAX_CALL_ID с параметрами по умолчанию сформирует код вызова текущего компонента с текущими параметрами и если вызвать его, то компонент просто обновится.
4. Обработка вызовов AJAX Все запросы происходят через интерфейс /bitrix/tools/ajax.php , рассмотрим его функционал. Вся его работа сводится к тому, чтобы подключить вызванный компонент и отдать пользователю в виде ответа на AJAX-запрос. Листинг файла:
<?php
define("NO_KEEP_STATISTIC", true); // Не собираем стату по действиям AJAX
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
if (strlen($_REQUEST['ajax_call'])==32 AND isset($_SESSION['ajax_components'][$_REQUEST['ajax_call']])) {
$component = $_SESSION['ajax_components'][$_REQUEST['ajax_call']];
$component['params'];
$component['params']['AJAX_ENABLED'] = "Y"; // В вызове компонента указываем, что он запрашивается через AJAX
$component['params']['AJAX_CALL'] = "Y"; // В вызове компонента указываем, что он запрашивается через AJAX
$component['params']['CACHE_TYPE'] = "N"; // В вызове компонента отключаем кэш
$component['params']['CACHE_TIME'] = "0"; // В вызове компонента отключаем кэш
// Дополнительная обработка переданных данных, они могут быть просто в строке запроса
/*
// ВНИМАНИЕ!!! Использование подмены параметров с переменной может быть небезопасно
foreach ($_GET as $get_key=>$get_value) {
if (!in_array($get_key, array("ajax_call")) AND $get_value) {
$component['params'][$get_key] = $get_value;
}
}
*/
$APPLICATION->IncludeComponent(
$component['name'],
$component['template'],
$component['params'],
false
);
}
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_after.php");
?>
В начале файла ставим константу NO_KEEP_STATISTIC, чтобы статистика не считала эту страницу. Далее подключаем prolog_before.php - это первая часть пролога, она необходима. Вторая часть пролога - это вывод первой части шаблона, нам нафик не надо. Если вы нормальный разработчик, то наверняка с этим сталкивались, должны знать. В скрипт поступили параметры, они все находятся в $_REQUEST. Просто черт его знает может там через GET, а может через POST они пришли. У всех запросов должен быть $_REQUEST['ajax_call'], без него это не запрос к AJAX. И по этому же ajax_call в сессии текущего пользователя $_SESSION['ajax_components'] должны быть указаны параметры вызываемого компонента. Если всё есть, начинаем обработку и подключение компонента.
Специально оставил закомментированный блок с foreach ($_GET as $get_key=>$get_value) - это пример как можно в параметрах подменить параметры. Закомментировал т.к. пока использовать не приходилось и этот код возвращает нас к вопросу безопасности и некрасивости кода.
В общем-то по этому файлу больше писать нечего, всё должно быть понятно. Теперь опишу некоторые сложности использования всего этого хозяйства. При вызове компонента с параметрами по умолчанию происходит обновление самого себя, но вызов должен производиться в некий контейнер. Этот контейнер определяется в шаблоне компонента, а при вызове этого же шаблона через AJAX контейнер просто не показывается. Это может быть сделано примерно так:
Т.е. при нормальной загрузке компонента на страницу <div id="some_text"> есть, а при вызове AJAX его уже не будет, но он остается на странице и результат AJAX в него будет загружен. Это не очень эстетично, поэтому еще предстоит что-то выдумать.
Другой момент - в компонентах необходима отдельная обработка вызовов AJAX. Но это не то чтобы неудобство, больше плюсов - компонент логически разделяется как бы на подчасти, каждая из которых вызывается при определенных событиях. Вот пример начальной части реального компонента:
<?
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
CModule::IncludeModule("bexx.shop");
// Обработка AJAX событий
$AJAX_CALL_ID = CBexxShop::ajaxId(); // Перенаправляем все запросы в главный компонент каталога
if (strlen($_REQUEST['ajax_call'])) { // Обработка событий AJAX
$arResult['AJAX_CALL_ID'] = $AJAX_CALL_ID;
if ($_REQUEST['do'] == "add2cart") { // Добавление в корзину
CModule::IncludeModule("sale");
CModule::IncludeModule("catalog");
$code = BexxAdd2Cart(intval($arParams['ID']), 1);
$arResult['ADD2BASKET_OK'] = CSaleBasket::GetByID($code);
$this->IncludeComponentTemplate("ajax_cart");
} elseif ($_REQUEST['do'] == "compare") { // Добавление товара в сравнение
if ($_REQUEST['set'] == 1) $_SESSION['compare'][intval($arParams['ID'])] = 1;
else unset($_SESSION['compare'][intval($arParams['ID'])]);
$this->IncludeComponentTemplate("ajax_compare");
}
} else { // нормальная обработка компонента
...
}?>
Это кусок кода с компонента, который выводит страницу товара. Здесь видны два события: add2cart и compare. После else идет нормальный компонент. Как видно, каждое событие AJAX обрабатывается отдельно, подключает свой шаблон ответа через $this->IncludeComponentTemplate(), передает в него свой $arResult.
Стандартный битриксовский AJAX не хуже. Только че-то он меня не прет. Сначала не знал как он вообще работает, теперь узнал и использовать точно не хочу. В принципе, его тоже можно подсадить на ссылки с классом ajax_link, использовать jQuery - тоже будет красиво и все такое. Кому интересно как работает AJAX битиркса на уровне компонентов, рекомендую поковырять компонент bitrix:sale.order.ajax - там все прелести видны. Я своего точного мнения на счет битриксовского AJAX не сформировал, о его достоинствах и недостатках с полной уверенностью говорить не могу.
PS: Да, еще AJAX Битрикса не решает главную (пусть и второстепенную) задачу AJAX - снижение нагрузки на сервер.
Вот это меня и поразило больше всего, когда я его изучал. По сути полностью отрабатывается вся страница, все компоненты до компонента c AJAX, а потом тупо обрубается RestartBuffer'ом Смысл от такого аякса? Проще уж страницу перезагрузить - та же нагрузка. А если аякс требуется на какой-то форме, допустим, форма фильтрации товаров, где при каждом изменении товаров происходит пересчитывание количества товаров - это ж за ddos'ить сервер можно AJAX-запросами. Как пример, фильтр на яндекс-маркете.
С синтаксисом <a> проблема конечно есть. Но кому важен синтаксис, можно не использовать обработку класс ajax_link, а вставлять onclick="..." - будет тоже самое и синтаксис в порядке.
По поводу просроченных сессий, пользователю будет обновляться текущая страница - восстанавливается сессия, далее начинает работать с AJAX. На самом деле длительности сессии вполне хватает для AJAX-запросов - они ж в течение просмотра сайта происходят. У кого не работает JS - 1% пользователей можно пренебречь, также как пользователями IE6.
По поводу просроченных сессий, пользователю будет обновляться текущая страница
- эмм, а что в /bitrix/tools/ajax.php или ajax_load() заставит обновиться страницу?
На самом деле длительности сессии вполне хватает для AJAX-запросов - они ж в течение просмотра сайта происходят.
Не факт. Например, отвлечет телефонный звонок, аська и т.п., а потом будешь долго жать на неработающие аякс-элементы т.к. $_SESSION['ajax_components'][$_REQUEST['ajax_call']] уже не будет. Еще немаловажный момент - как при таком подходе быть со страницами, где "аяксуемый" компонент зависит от результата работы другого (обычно от фильтров)?
У кого не работает JS - 1% пользователей можно пренебречь, также как пользователями IE6.
В общем, к чему я веду - самая правильная идеология именно у коробочного аякса, только вот удобство пользования (читай полнота и качество реализации) желает лучшего.
P.S. Сам я тоже не пользуюсь бусовским аяксом и тоже больше двух лет ищу способ как красиво подружить внешние бибилиотеки с идеологией системы, но пока все вокруг да около.
Группы на сайте создаются не только сотрудниками «1С-Битрикс», но и партнерами компании. Поэтому мнения участников групп могут не совпадать с позицией компании «1С-Битрикс».