[EVO] Ajax. Финальный правильный метод №4

Предлагаемые ранее методы черезжопные изначально.

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

Использование jQuery.load это костлявый ajax, т.к. реально грузится вся страница целиком и уже только потом из этой страницы выбирается нужный HTML блок. При этом нет возможности получать ответ в JSON без дополнительных плагинов и модификаций сниппетов.

Разруливание роутингом на уровне плагина под событием OnPageNotFound — это конечно решение, но если вдруг кто-то создаст документ с алиасом указаным в switch, то весь ajax полетит к чертям, а программист будет искать ошибку пока не поседеет.

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

Использование index.php как отправная точка — единственное верное решение. Тем более, там уже заложена возможность запуска в режиме API. Итак, создаем в корне сайта (рядом с index.php) какой-нибудь файлик (допустим ajax.php)

define('MODX_API_MODE', true);
include_once(dirname(__FILE__)."/index.php");
$modx->db->connect();
if (empty ($modx->config)) {
    $modx->getSettings();
}
$modx->invokeEvent("OnWebPageInit");

if(!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || (strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest')){
	$modx->sendRedirect($modx->config['site_url']);
}
$out = $modx->runSnippet('test');
$return = array('message' => $out);
echo json_encode($return);


Как итог, в созданном файле автоматически задаются все те же параметры как и во всем движке. Вызывается событие OnWebPageInit (на случай если на это событие подвешены какие-то плагины типа LoadElement или автозагрузчики классов типа Composer).
Более того, сниппеты занимаются своими делами, а внутри файла мы уже задаем непосредственно логику для запуска этих сниппетов и обработку данных без внедрения дополнительных сущностей (сниппетов).

Банальный пример. Необходимо вернуть или в HTML или в json. Для этого последние 3 строки файла перепишем так:
$out = $modx->runSnippet('test');
if(isset($_REQUEST['json'])){
   $out = json_encode(array('message' => $out));
}
echo $out;

59 комментариев

avatar
Мне тоже этот метод нравится, в основном я так и работаю с AJAX, но бывает что и создаю документы для конкретного Action с вызовом некэшируемого сниппета обработчика и ставлю Content-type: application/json
avatar
Я тоже иногда документы создаю. Но тем не менее мне такой подход не очень нравится, т.к. приходится создавать одноразовые сниппеты, которые засоряют кеш и мозолят глаза. Я на эту тему вот тут еще мысли свои описал для полноты картины.
avatar
Полностью согласен с тобой)
avatar
if(isset($_REQUEST['json'])){
   $return = json_encode(array('message' => $out));
}
echo $out;


и что это нам даст? :)
avatar
$return = $out ))
Пофиксил…
avatar
Да я то понял, просто зайдет кто-нибудь, скопирует код — а потом сюда же с криком «у меня ваш самый правильный метод ну нифига не работает, json-а не возвращает» и вернется :)))
avatar
Женя логику показал а не 100% рабочий пример. Кому надо и кто потянет без заминок разберётся.
avatar
Я лишь указал на небольшую очепятку — см цитату :)
avatar
Для этого есть личка ;)
avatar
:)
avatar
У меня такой способ не прокатил. Парсер выдает ошибку в вызове runsnippet. А если делаю через сниппет в дереве то все ок.
avatar
При таком вызове не определены documentObject и documentIdentifier. А некоторые сниппеты к ним обращаются если не переданы дополнительные параметры.
avatar
Для полного счастья перед echo не хватает:
header('content-type: application/json');

Ваш метод можно стандартизировать, добавив вместо вызова сниппета проверку$_REQUEST['action'] (к примеру) и далее switch($action)… Но сам я приверженец документного AJAX в MODx. Скрыть папку API от клиента и роботов не трудно, а сниппет можно сделать один универсальный и вызывать его с нужными параметрами.
avatar
сниппет можно сделать один универсальный и вызывать его с нужными параметрами.
Цикл жизни ajax запроса в вашем случае:
— Инициализируется ядро MODX
— Получается documentObject страницы
— Получается сниппет
— Разбираются параметры
— Выполняется код
— Препарируется результат шаблонизатором MODX

Цикл жизни AJAX запроса в моем случае:
— Инициализируется ядро
— Выполняется код

Ну и наконец в вашем случае нужно еще проделать 1 совсем несложный этап:
Скрыть папку API от клиента и роботов
avatar
Тут приходится выбирать между удобством и максимальной производительностью. Кстати, в вашем примере также вызывается сниппет, а закрыть от роботов ajax.php не будет лишним.
avatar
Ваш подход хорош и логичен. Думаю, в него нужно добавить параметры action и mime и применять в качестве альтернативы index-ajax.php.
avatar
Кстати, в вашем примере также вызывается сниппет
Вызывается. Но его не нужно разбирать парсеру и определять параметры.
Да и вызов сниппета это лишь пример — можно и сразу код фигачить. В документах увы так нельзя.
avatar
В офф топ сказать, был один персонаж что выдумал тип документа позволяющий пыху писать прямо в контент — официальное сообщество не одобрило. Ну это я так, к слову.
Комментарий отредактирован 2014-05-04 16:45:48 пользователем vasenin26
avatar
Да хоть в txt файлах пыху выполняйте. Мне от этого не холодно, ни жарко
avatar
случайно нашел плагин:
github.com/ghettovoice/AsyncDocs-EVO
avatar
что-то не могу с ним разобраться
avatar
Вот этот работает,
github.com/Husband/AjaxifyEvo
но не изменяются мета-теги при переходе по страницам
Комментарий отредактирован 2014-09-03 19:44:25 пользователем nohc
avatar
Очень хочу во всем этом разобраться, но для наглядности не хватает примеров. Думаю, не только мне. Может кто-нибудь привести пример, как по этому методу сделать, допустим, пагинацию?
avatar
Создать файл, как написано, в нем вызывать DocLister. Обработчиком клика по ссылкам с номерами страниц посылать запрос с параметром page к файлу.
Но мне кажется, что этот способ хорошо подойдет для одностраничного сайта. А если нужно, чтобы работало и без ajax, то я бы выбрал метод с прерыванием парсера. Описано здесь modx.pro/development/3139-foundations-of-ajax/ — только я делал с помощью плагина.
avatar
Действительно не понятно, как получить доступ к содержимому этого файла, jquery load выдаёт ошибку, так же и с post. Догадался таки, пробую же на Revolution…
avatar
А как с Revo быть?
avatar
Как итог, в созданном файле автоматически задаются все те же параметры как и во всем движке.
Если так, то почему теги modx в вызове чанков и сниппетов не отображаются? Вместо них код: [+placeholder+], [!snippet!].
avatar
подскажите пожалуйста не могу разобраться, у меня все равно страницу перезагружает((
avatar
получаю ошибку 500
код сниппета
<code>define('MODX_API_MODE', true);
include_once(dirname(__FILE__)."/index.php");
$modx->db->connect();
if (empty ($modx->config)) {
    $modx->getSettings();
}
$modx->invokeEvent("OnWebPageInit");

if(!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || (strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest')){
        $modx->sendRedirect($modx->config['site_url']);
}
/*_____start_____*/

			$p['parents'] = $_POST['docid'];
			$p['id'] = $_POST['id'];
			$p['tpl'] = 'proj.tpl';
			$p['start'] = 1;
			$p['display'] = '10'; // all
			$p['depth'] = 1;
			$p['sortDir'] = 'ASC';
			$p['sortBy'] = 'pub_date';
			$p['dateSource'] = 'pub_date';
			$p['noResults'] = 'Каталог пуст.';
			$p['paginate'] = 1;
			$p['hideFolders'] = 1;
			$p['debug'] = 1;
			$p['tplPaginateNext'] = '@CODE:
										<div class="more-projekt">
											<a href="[+url+]" class="transition" id="ditto_next_link">еще 8 проектов</a>
											<div class="transition">
												<i class="fa fa-refresh"></i>
											</div>
										</div>';

			$out = $modx->runSnippet('Ditto', $p);

/*_____end_____*/

if(isset($_REQUEST['json'])){
   $out = json_encode(array('message' => $out));
}
echo $out;</code>
код страницы
<code>
  <span class="ajax[+alias+]">
	[[Ditto?parents=`[+id+]` &tpl=`proj.tpl` &display=`10` &depth=`1` &sortDir=`ASC` &dateSource=`pub_date`
	&sortBy=`id` &paginate=`1` &hideFolders=`1` &start=`0` &id=`p` &tplPaginateNext=`next.tpl`]]

	[+p_next+]
	</span>
<script>
$(function() {
  $('#ditto_next_link').on('click', function(event) {
  	event.preventDefault();

		$.ajax({
		    url:'[(site_url)]ajax.php',
		    type:'post',
		    data: 'id=p&docid=[+id+]',
				success: function(data){ //Функция, которая будет выполняться при успехе
					$('.ajax[+alias+]').html(data); // alert( data )
				}
		})

  });
});
</script></code>
Комментарий отредактирован 2016-10-05 15:31:14 пользователем doc555
avatar
Ну так смотри лог. 500 ошибка это php
avatar
в логах ничего; сам сниппет работает как надо…
avatar
500 — ошибка. Значит она есть. Либо смотрите, почему пустые логи, либо начните с пустого файла и echo '11'; и постепенно дописывая код, пока ошибка не вылезет.
Сегодня юзал этот код для аякса, всё работает, как часики.
Комментарий отредактирован 2016-10-06 13:50:19 пользователем 1px
avatar
Столкнулся с проблемой.
Сделал запрос по методу, приведенному тут.
Если в файле ajax.php пишу
$out = $modx->runSnippet('models');

то он возвращает строку Array

Если в файле пишу сразу код сниппета, который просто выводит строку из базы, то все нормально работает.

В чем может быть проблема?
avatar
Код сниппет в студию
avatar
Код вот такой

В браузере нормально открывается, выводит что должен.
<code>$mark = $_GET['mark'];
$mark = (int)$mark;
if(isset($mark) and $mark <> 0) {
	$sql = "SELECT id,pagetitle,alias FROM modx_site_content WHERE parent=$mark and published=1 and deleted=0 order by pagetitle";
	$result = $modx->db->query($sql);
	while($row = $modx->db->getRow( $result ) ){
   $url=$modx->makeUrl($row['id']);
		$models .= "<option value='".$url."'>".$row['pagetitle']."</option>";	
	}   
}
else {
	$sql = "SELECT id,pagetitle FROM modx_site_content WHERE parent=324 and published=1 and deleted=0 order by pagetitle";
	$result = $modx->db->query($sql);
	while($row = $modx->db->getRow( $result ) ){
   $url=$modx->makeUrl($row['id']);
		$models .= "<option value='".$url."'>".$row['pagetitle']."</option>";	
	}
}

echo $models;</code>
Комментарий отредактирован 2018-03-06 15:36:47 пользователем dmichael
avatar
Как минимум, надо не echo, а return делать в современных сниппетах :) А вот уже потом в ajax.php — echo $out;
avatar
Попробовал, эффекта нет.
Вот сниппет на страничке выводится
www.test1.sitefarm.ru/models.html?mark=360
www.test1.sitefarm.ru/models.html?mark=340

а если через аякс запрашивать ajax.php с запуском этого сниппета, выводит пустую строку.

Комментарий отредактирован 2018-03-07 02:44:11 пользователем dmichael
avatar
Раньше вы делали неправильно, потом сделали правильно, а говорите, что нет эффекта :) Возможно, вы еще где-то что-то делаете неправильно — например, вызываете некорректно ajax.php или некорректно возвращаете из него ответ, а может вообще возвращаете не то, что собирались вернуть :)
avatar
Возможно, что-то я делаю неправильно. Поэтому и пишу тут.


Вот страничка с таким вот кодом:
www.test1.sitefarm.ru/test4.html
<script src="js/jquery-1.12.0.min.js"></script>
<p>[!test1!]</p>
<script>
$(document).ready(function() {
   		//console.dir(www);
		$.ajax({
		   dataType : 'html',
           type : 'POST',
		   url: 'http://www.test1.sitefarm.ru/ajax1.php',
		   success: function(data){
					console.log(data);
					alert(data);
		   }
		 });

		
		
		
	
});
	
</script>



Как видите в ней сначала отрабатывает сниппет.
Код сниппета:
<?php
return "asd";




Вот код файла ajax1.php
Закомментировал проверку на xmlhttprequest

<?php
define('MODX_API_MODE', true);
include_once(dirname(__FILE__)."/index.php");
$modx->db->connect();
if (empty ($modx->config)) {
$modx->getSettings();
}
$modx->invokeEvent(«OnWebPageInit»);

/*if(!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || (strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest')){
$modx->sendRedirect($modx->config['site_url']);
} */
$out = $modx->runSnippet('test1');

echo $out;

?>


Если вызвать напрямую, работает
www.test1.sitefarm.ru/ajax1.php


Запрос через аякс упрямо возвращает строку Array

www.test1.sitefarm.ru/ajax1.php


Что я делаю не так?
avatar
Можно вместо echo сделать var_dump, тогда будет понятнее, что вообще возвращается.
avatar
Напрямую возвращает вот что
prntscr.com/io1buh

/home/c482/test1.sitefarm.ru/www/ajax1.php:15:string 'asd' (length=3)


аяксом также Array возвращает.
Комментарий отредактирован 2018-03-07 12:07:14 пользователем dmichael
avatar
dataType : 'html',

это не нужно
avatar
Убрал, ничего не изменилось
avatar
Может у меня какая-то непонятная хрень с php или настройкой сервера?
Потому что еще есть проблема со входом пользователя
prntscr.com/io1du6
Комментарий отредактирован 2018-03-07 12:08:53 пользователем dmichael
avatar
А если перед echo $out; вставить
$modx->logEvent(1,1,$out,$out);

что в логах «просмотр событий» показывает?
avatar
Сделал так:
$modx->logEvent(1,1,"asd","asd");
$out = $modx->runSnippet('test1');
$modx->logEvent(1,1,$out,$out);
echo $out;

В логах ничего не выводится.
Если напрямую вызвать, выводится два раза:http://prntscr.com/io1lq7
Комментарий отредактирован 2018-03-07 12:24:06 пользователем dmichael
avatar
Скорее всего на OnWebPageInit висит плагин, который и мешает.
avatar
Спасибо Pathologic!
Нашел вредный плагин!
Вторую ошибку с weblogin также вызывал плагин userHelper
Комментарий отредактирован 2018-03-07 12:36:28 пользователем dmichael
avatar
Я ж тебе говорил, что при этом методе OnWebPageInit — это больное место :)
avatar
Надо просто понимать, что к чему (:
avatar
Последовательным переносом строки с logEvent установил, что виновата строка $modx->invokeEvent(«OnWebPageInit»);
Закомментировал, стало работать как надо:)
avatar
Покажите рабочий пример, как на этом методе реализовать запуск ФормЛистера в модальном окне.
avatar
Ни кто не пользуется данным методом?
avatar
Если форм на сайте несколько и отправляем их через один файл для ajax-запросов, то вместо строчек из примера в этом топике
$out = $modx->runSnippet('test');
$return = array('message' => $out);
пишем, например:
$action = isset($_REQUEST['formid']) ? $_REQUEST['formid'] : null;

switch ($action) {
  case 'callback':
    $return = $modx->runSnippet('FormLister', array('formid'=>'callback', 
        'config'=>'callback:core')); 
  break;
}

На сайте делаем вызов
[!FormLister? &formid=`callback` &config=`callback:core`!]

В директории с конфигурацией FormLister создаём файл с настойками, например callback.json, в котором все настройки и указываем.

А как оправить форму через ajax на нужный файл, надеюсь, вы знаете.
avatar
Спасибо! Подскажите и аякс, пожалуйста. Возможно, я методом проб и ошибок что-то соображу, типа такого:
$(document).on('click','.form',function(e){
	e.preventDefault();
	$('#content').load('/ajax.php',{action:'callback'});
});

Но чутье мне подсказывает, что не так надо делать.
avatar
А это решение вам не помогло?
avatar
Да, оно работает. Оно работает и без этого «финального метода №4». Но кто-то говорил, что там неправильный аякс. И хотелось бы сделать наконец-то все правильно.
Объясню ситуацию. Лейдинг. Начальство просит множество форм на странице, практически в каждом блоке свой лид. В итоге куча модальных окон с формами и просто формы без модальных окон… куча кода, куча вызовов Формлистера. Везде нужен аякс. Кошмар, который я хочу причесать.
avatar
Укажите в атрибуте action ваших форм путь к файлу из этого метода и скрипт из вашего комментария по ссылке выше будет отправлять форму и подставлять результат.
В зависимости от того, чем отличаются формы, вы уже решите, обрабатывать их одним конфигом Формлистера или под каждую форму делать свой конфиг. Соответственно и в аякс-файле можете обрабатывать все формы через один action и вызов Формлистера или несколько.
Не ясно в чем ещё трудность.
avatar
Вариант для 2.0:

<?php
define ('MODX_API_MODE', true);
define ('IN_MANAGER_MODE', true);
define ('MODX_BASE_PATH', dirname(__DIR__) . '/');
define ('MODX_BASE_URL', '/');
define ('MODX_SITE_URL', 'https://sitename.ru/');
include_once __DIR__ . '/../index.php';
$modx->db->connect();
if (empty ($modx->config)) {
    $modx->getSettings();
}
$modx->invokeEvent("OnManagerPageInit");
if (!MODX_CLI) die('Run this script with console');
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.