GrabDuck

Правильный AJAX в 1С-Битрикс | Интересные задачи верстки

:

В CMS 1С-Битрикс есть свой встроенный ajax в нескольких компонентах. В этом встроенном ajax есть один существенный минус — страница все равно полностью выполняется.

Кроме встроенного в стандартные компоненты варианта ajax, можно самому написать более правильную реализацию на битриксовской библиотеке. Про встроенную библиотеку можно прочитать у Рамиля Юналиева:
http://yunaliev.ru/2010/02/bitrix-ajax/

Здесь я покажу как мне кажется более правильный вариант ajax, именно организацию работы. Отправлять сами ajax запросы я буду на jquery. Больший акцент я сделаю именно на серверной стороне, но чтобы получился законченный пример клиентскую сторону мы тоже расссмотрим.

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

Клиентская часть. Демонстрация

Для работы AJAX как вам наверное известно нужно две части — клиентская и серверная. На клиенте нужно написать javascript код который будет отправлять сам запрос без перезагрузки страницы. На сервере нужно поместить какой то html файл или скрипт, который будет давать ответы на клиентские запросы.

Начнем с клиентской стороны.

Как это все будет выглядет для пользователя:
http://verstaem.com/examples/ajax-1c-bitrix/

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

Вот html код корзины:

<div class="basket">
	<h2>Корзина</h2>
	<span class="current">2 800 руб. — 1 шт.</span>
	<a href="javascript:void(0)">Обновить</a>
</div>

Вот скрипт на jquery который отправляет ajax запрос:

$(document).ready(function(){
	$(".basket a").click(updateBasket)
})

function updateBasket()
{
    $.ajax({
        url: "/ajax.handler.php",
        type: "POST",
        dataType: "html",
        data: "PAGE=BASKET",
        success: function(data){
		$('.basket .current').html(data)
        }
    });
	
}

То есть при нажатии на ссылку «Обновить» происходит отправка ajax запроса к ajax.handler.php из корня сайта. Полученный ответ вставляется в <span class=’current’></span>. Все довольно просто.

На самом деле это не AJAX, а AJAH, так как в ответе приходит уже готовый html, а не xml из которого еще нужно забрать данные. Но суть примерно одна.

Можно добавить всяких украшательств, например анимированный gif загрузки, но суть останется такой же.

Работа на серверной стороне

Теперь о том как такое должно быть организовано на серверной стороне.

Создадим файл ajax.handler.php в корне сайта, на котором установлен 1С-Битрикс. В этом файле напишем

<?require_once($_SERVER['DOCUMENT_ROOT']. "/bitrix/modules/main/include/prolog_before.php");

Что делает эта строка? Подключает служебную часть пролога в битриксе. При этом шаблон сайта не подключается. С помощью этой строки мы получаем доступ к АПИ битрикса и при этом никакого лишнего html кода не выводится.

Впринципе можно написать код, который будет отвечать на ajax запросы прямо здесь. Можно не заморачиваться ни с какими компонентами и отдельными файлами. Но что будет если у нас будет несколько разных видов запросов? Например если один вид ajax запроса обновляет корзину, второй совершает заказ, третий удаляет из корзины один товар и т.д. В рамках одного файла работать будет не очень удобно.

Чтобы разграничить ответы на разные запросы, я добавлю такой код в файл ajax.handler.php:

<?require_once($_SERVER['DOCUMENT_ROOT']. "/bitrix/modules/main/include/prolog_before.php");

if($_REQUEST["PAGE"] == "BASKET"){
	require_once($_SERVER["DOCUMENT_ROOT"] . "/handlers/basket.php");
}

То есть, если в запросе переменная PAGE равна BASKET, то подключим файл basket.php, который находится в папке handlers. Таким образом, для разных ajax запросов можно будет просто добавлять новые условия в файле ajax.handler.php, и не думать про взаимодействие с другими запросами. Само собой нужно создать папку handlers в корне сайта и внутри нее файл basket.php.

В файле /handlers/basket.php уже можно собирать данные по корзине, оформлять это все в html и отдавать по запросу. Но если прямо тут писать код, то страница грязная получится, то есть программный код собирающий данные будет прямо рядом с кодом который эти данные отображает в виде html. Поэтому я буду делать стандартный компонент битрикса и просто вставлю его на эту страницу. В стандартном компоненте можно разделить логику и представление. Под каждый вид ajax запрос нужно будет создавать свои отдельные компоненты. Код вызова компонента на странице basket.php

<?
$APPLICATION->IncludeComponent("ajax:basket", ".default", array(), false);
?>

Если у компонента не много параметров, то можно прямо внутри условия в файле /ajax.handler.php его вызвать и не выносить вызов в отдельный файл.

<?require_once($_SERVER['DOCUMENT_ROOT']. "/bitrix/modules/main/include/prolog_before.php");

if($_REQUEST["PAGE"] == "BASKET"){
	$APPLICATION->IncludeComponent("ajax:basket", ".default", array(), false);
}

Теперь напишем сам компонент. Создадим папку /bitrix/components/ajax. Внутри создаем подпапку basket с самим компонентом. На том как правильно писать компонент я не буду останавливаться подробно, вот основные сведения которые нужно знать про компоненты:
http://dev.1c-bitrix.ru/api_help/main/general/component20/index.php

Приведу листинг файла component.php

<?
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();

CModule::IncludeModule("sale");

$arBasketItems = array();

$dbBasketItems = CSaleBasket::GetList(
        array(
            "NAME" => "ASC",
            "ID" => "ASC"
        ),
        array(
            "FUSER_ID" => CSaleBasket::GetBasketUserID(),
            "LID" => SITE_ID,
            "ORDER_ID" => "NULL",
            "CAN_BUY" => "Y",
        ),
        false,
        false,
        array("ID", "CALLBACK_FUNC", "MODULE", 
              "PRODUCT_ID", "QUANTITY", "DELAY", 
              "CAN_BUY", "PRICE", "WEIGHT")
    );

while ($arItem = $dbBasketItems->Fetch())
{
    if(strlen($arItem["CALLBACK_FUNC"]) > 0)
    {
        CSaleBasket::UpdatePrice(
            $arItem["ID"], 
            $arItem["CALLBACK_FUNC"], 
            $arItem["MODULE"], 
            $arItem["PRODUCT_ID"], 
            $arItem["QUANTITY"]
        );
        $arItem = CSaleBasket::GetByID($arItem["ID"]);
    }
    
    $arResult["TOTAL_PRICE"] = $arResult["TOTAL_PRICE"] + $arItem["PRICE"] * $arItem["QUANTITY"];
    $arResult["ITEMS"][$arItem["PRODUCT_ID"]] = $arItem;
}

$arResult["TOTAL_PRICE"] = SaleFormatCurrency($arResult["TOTAL_PRICE"], 'RUB');

$this->IncludeComponentTemplate();

Последняя строка подключает шаблон компонента. Вот код шаблона компонента:

<?if(count($arResult["ITEMS"]) > 0):?>
    <?=$arResult["TOTAL_PRICE"]?> — <?=count($arResult["ITEMS"])?> шт.
<?else:?>
    Корзина пуста
<?endif;?>

Вот такой небольшой шаблон.
Проверить работоспособность серверной части можно открыв в браузере «адрес сайта/ajax.handler.php?PAGE=BASKET». После открытия страницы должен появиться примерно такой текст на белом фоне «6 830 руб. — 2 шт.», то есть сумма и количество товаров или текст «Корзина пуста» если у вас нет ни одного товара в корзине.

Итого, какие преимущества дает вышеописанная организация AJAX:

  • Добавление новых запросов без труда
  • Все старые запросы обрабатываются независимо, поэтому их удобно редактировать
  • Во время ajax запроса выполняется только код, который необходим для ответа. Ничего лишнего не выполняется
  • Благодаря использованию стандартных компонентов получаем удобное разделение логики и представления

Если у вас есть какие то дополнения к описанной выше организации работы ajax или ajax запросы у вас обрабатываются как то по другому — пишите в комментариях.