Ajax-запросы нативными средствами Joomla

Небольшая заметка о том, как делать ajax-запросы штатными средствами без использования дополнительных js-библиотек (jQuery, etc). Joomla 3 и Joomla 4 предоставляют небольшую обёртку для конструирования XMLHttpRequest. В целом синтаксис очень похож на тот же jQuery Ajax, поэтому заменить его будет очень легко.

В <head> страницы можно увидеть core.js, в котором есть немало любопытных функций для работы с фронтом на Joomla. Об одной из них (получение данных из php в js) писалось здесь: Разработка форм обратной связи для магазинов на Joomla 3. Для создания ajax-запросов нам пригодится Joomla.request.

Ajax в Joomla - Joomla.request

Joomla 3

Представим, что Вам нужно просто отправить запрос, не получая никаких данных:

Joomla.request({
	 			url: 'index.php?option=com_example&view=example'
      })

Выбор метода запроса (POST или GET)

Joomla.request({
				url: 'index.php?option=com_example&view=example',
      	method: 'POST'
    	})

Если выбран метод запроса POST. то автоматически будет отправляться заголовок X-CSRF-Token и сам CSRF-токен, который можно и нужно проверить в точке входа.

Проверка CSRF-токена в точке входа

Для этого используем метод Session::checkToken. В качестве параметра можно указывать как post (это значение по умолчанию), так и get.

use Joomla\CMS\Session\Session;
use Joomla\CMS\Language\Text;
Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));

Проверить наличие CSRF-токена на странице можно в консоли браузера:

Joomla.getOptions в консоли браузера
Joomla.getOptions в консоли браузера

Если CSRF-токена в Joomla Script options нет, то в PHP можно его добавить с помощью класса HTMLHelper.

HTMLHelper::_('form.csrf');

Установка заголовков ajax-запроса

Есть возможность установки своих заголовков ajax-запроса. Например, заголовка Cache-Control, тогда будет корректно работать условная ajax-корзина, каждый раз получая свежие данные. Также можно устанавливать свои уникальные заголовки, если это необходимо.

Joomla.request({
  url: 'index.php?option=com_example&view=example',
  method: 'POST',
  headers: {
  		'Cache-Control' : 'no-cache',
    	'Your-custom-header' : 'custom-header-value'
  	}
})

Отправка данных через ajax в Joomla

Отправка данных на выбранный url происходит согласно спецификации. Чаще всего это или строка или объект. Для того, чтобы данные пришли в удобном виде стоит применить к передаваемому объекту JSON.stringify, так же указать заголовок запроса Content-type:applictation/json. Хорошая статья о FormData на learn.javascript.ru в помощь.

Joomla.request({
  url: 'index.php?option=com_example&view=example',
  method: 'POST',
  headers: {
          'Cache-Control' : 'no-cache',
        	'Your-custom-header' : 'custom-header-value',
          'Content-Type': 'application/json'
      },
  data:  JSON.stringify({
  	'key1' : 'value1',
    'key2' : 'value2'
  })
})

Если метод запроса POST, в data передается просто строка и не установлен заголовок Content-Type, то он (Content-Type) принимает значение application/x-www-form-urlencoded. В php получаем данные следующим образом:

use Joomla\CMS\Factory;
$data = Factory::getApplication()->input->json->getArray();

Callback-функции ajax-запроса в Joomla

Joomla.request({   
  url: 'index.php?option=com_example&view=example',   
  method: 'POST',   
  headers: {           
    'Cache-Control' : 'no-cache',         
    'Your-custom-header' : 'custom-header-value',
    'Content-Type': 'application/json'
  },
  data:  JSON.stringify({
  	'key1' : 'value1',
    'key2' : 'value2'
  }),
  onBefore: function (xhr){
    // Тут делаем что-то до отправки запроса. 
    // Если вернём false - запрос не выполнится
  },
  onSuccess: function (response, xhr){
  	// Тут делаем что-то с результатами
    
    //Проверяем пришли ли ответы
					if (response !== ''){
						let jshopping_cart = JSON.parse(response);
          	// И дальше делаем, например, супер-аякс-корзину-под-joomshopping
          }
   },
  onError: function(xhr){
  	// Тут делаем что-то в случае ошибки запроса. 
    // Получаем коды ошибок и выводим сообщения о том, что всё грустно.
  }
})

Запрос обёрнут в try-catch, поэтому если запрос не проходит - смотрим в консоль, там должна логироваться ошибка.

Флаг perform

Это значение по умолчанию установлено в true. Если установить в false - запрос не будет выполняться, а так же не будет вызываться callback-функция onBefore. Полезно в тех случаях, когда не нужно в процессе разработки некоторое время дёргать лишний раз сервер.

Joomla.request({   
  url: 'index.php?option=com_example&view=example',   
  method: 'POST',   
  headers: {           
    'Cache-Control' : 'no-cache',         
    'Your-custom-header' : 'custom-header-value',
    'Content-Type': 'application/json'
  },
  data:  JSON.stringify({
    'key1' : 'value1',
    'key2' : 'value2'
  }),
  onBefore: function (xhr){
    // Тут делаем что-то до отправки запроса. 
    // Если вернём false - запрос не выполнится
  },
  onSuccess: function (response, xhr){
    // Тут делаем что-то с результатами
    //Проверяем пришли ли ответы
        if (response !== ''){
        		let jshopping_cart = JSON.parse(response);
            // И дальше делаем, например, супер-аякс-корзину-под-joomshopping
          }
   },
  onError: function(xhr){
    // Тут делаем что-то в случае ошибки запроса. 
    // Получаем коды ошибок и выводим сообщения о том, что всё грустно.
  },
  perform : false // вся проделанная выше работа бесполезна. Запрос прерван.
})

Joomla 4

В Joomla 4 метод переписан, но для простых смертных всё остается так же. Добавляется лишь ещё одна callback-функция onComplete, которая выполняется в любом случае - как после успешного запроса, так и в случае ошибки.

Joomla.request({
	url: 'index.php?option=com_example&view=example',
  method: 'POST',
  headers: {
  	'Cache-Control' : 'no-cache',
    'Your-custom-header' : 'custom-header-value',
    'Content-Type': 'application/json'
    },
	data:  JSON.stringify({
    'key1' : 'value1',
    'key2' : 'value2'
  }),
  onBefore: function (xhr){
    // Тут делаем что-то до отправки запроса.
    // Если вернём false - запрос не выполнится
  },
  onSuccess: function (response, xhr){
   		 // Тут делаем что-то с результатами
   		 //Проверяем пришли ли ответы
   		 if (response !== ''){
   					 let jshopping_cart = JSON.parse(response);
   				 // И дальше делаем, например, супер-аякс-корзину-под-joomshopping
    		}
  },
  onError: function(xhr){
  		  // Тут делаем что-то в случае ошибки запроса.
    		// Получаем коды ошибок и выводим сообщения о том, что всё грустно.
  },
  onComplete: function (xhr){
    		// Тут что-то делаем в любом случае после ajax-запроса. 
        // в не зависимости от результата.
  }
});

Приветствую аргументированные замечания и пожелания к заметке.

@sergeytolkachyov
11.11.2021 20:05 UTC
Первоисточник

Комментарии

@Akuma
11.11.2021 15:34 UTC
+2

Fetch?

@mobi
11.11.2021 16:24 UTC
+1

У Joomla.request есть одно преимущество перед fetch: для POST-запросов автоматически добавляется CSRF-токен (если он, конечно, на странице задан вызовом HTMLHelper::_('form.csrf')).

@SergeiMinaev
11.11.2021 15:37 UTC
+1

Joomla 3 и Joomla 4 предоставляют небольшую обёртку для конструирования XMLHttpRequest

В чём смысл использовать XMLHttpRequest, когда более современный fetch не поддерживается только в IE?

@sergeytolkachyov
11.11.2021 15:46 UTC
0

Я про fetch не знал, хотя возможно, что он давно уже существует. В своей работе сталкиваюсь в основном с бэком. Насколько велика разница между XMLHttpRequest и fetch кроме синтаксиса?

11.11.2021 17:28 UTC
0

Существует давно, года с 2015-го. Принципиальной разницы нет. Насколько я знаю, основная фишка fetch - он возвращает промисы, которые, соответственно, можно чейнить. За счёт этого определённые штуки получаются компактнее.

11.11.2021 17:32 UTC
0

Фронтовые нюансы, в общем и целом) Это писалось больше для тех, кто подключает jquery, а порой и не один экземпляр ради того, что уже реализовано в ядре CMS.

@zkrvndm
16.11.2021 16:34 UTC
0

Поддерживаются ли промисы? Например, тот же jQuery.ajax() умеет в промисы, достаточно добавить await, чтобы убедиться, а здесь с этим как?

@sergeytolkachyov
16.11.2021 17:48 UTC
0

Почитал про промисы - такого, чтоб цепочки делать нет. В тройке точно, в четвёрке надо посмотреть, но вроде тоже нет. Только описанные колбэки.

17.11.2021 02:22 UTC
0

Промисы это не про цепочки, промисы это про удобство. Нет смысла использовать Joomla .request() когда из под корокби в Joomla 3 идет библиотка jQuery, при этом она уже подключена, делать ничего не надо. И самое главное ее достоинство, что с ней можно использовать await! Просто оцените насколько удобнее делать запросы:

response = await jQuery.ajax({
    url: '/',
    method: 'POST',
    headers: {
        'x-csrf-token': Joomla.getOptions('csrf.token')
    },
    data: {
        key1: 'value1',
        key2: 'value2'
    }
});

console.log("Ответ сервера:\n" + response);

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

14.11.2022 08:35 UTC
+1

В Joomla 4 добавили флаг promise, чтобы вызывать Promise:

Joomla.request = (options) => {
    // Prepare the options
    const newOptions = Joomla.extend({
      url: '',
      method: 'GET',
      data: null,
      perform: true,
      promise: false,
    }, options);

// Return a Promise
    if (newOptions.promise) {
      return new Promise((resolve, reject) => {
        newOptions.perform = true;
        createRequest(resolve, reject);
      });
    }

Ещё добавили очередь (FIFO queue of requests to execute serially) в виде Joomla.enqueueRequest.

Поизучать исходник можно https://github.com/joomla/joomla-cms/blob/4.2-dev/build/media_source/system/js/core.es6.js

@mobi
16.11.2021 20:14 UTC
0

Нативно — нет (скорее всего из-за обратной совместимости). Но если очень хочется, то никто не запрещает обернуть самостоятельно, скорее всего должно получиться как-то так:


const jRequest = url => new Promise((resolve, reject) => {
    Joomla.request({
        url: url,
        onSuccess: (_, response) => resolve(response),
        onError: reject,
    });
});
17.11.2021 02:28 UTC
0

Это бессмысленный велосипед, так как Joomla поставляется с уже подключенными jQuery, выше я привел пример.

@sergeytolkachyov
17.11.2021 04:22 UTC
0

Я пытаюсь представить задачи, в рамках которых может потребоваться await на сайте. В joomla ajax-запросы делаются для получения данных из модуля, плагина или (реже) компонента на том же сайте. Физически запрос идёт на тот же сервер и если он отдал вёрстку, то и ответ на запрос вряд заставит себя долго ждать. В Joomla 4 есть rest API из коробки, но вряд ли фронт будут писать на joomla.request. Запросы к удалённому серверу, от ответа которого зависят данные на странице - согласен. Но мне кажется, что joomla.request больше для внутреннего пользования. Представить сайт, где внешних запросов нет, анимации в дизайне сделаны на css. Тогда jquery не слишком нужен. И сеошник будет спокойнее, зная, что не грузится ещё одна неиспользуемая библиотека от 60 до 200кб весом.