Разделы

MODX && Ajax

В последнее время участились вопросы «хочу сделать сайт на ajax», «как подгружать разделы через ajax» и тд.

Вообще, судя по всему, MODX настолько дружелюбный, что многие разработчики и не собираются учиться программировать. То есть, они требуют готового ответа (расширения) — вынь да положи. Ссылки приводить не буду, достаточно просто поглядеть в блог «Вопросы».

Для тех людей, кому нужна удочка, а не рыба, я расскажу немного про Ajax.

Общие вопросы


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

На это можно посмотреть на примере голосования за топик. Не стесняйтесь, нажмите на стрелку «вверх» внизу топика — увидите, как она поменяется =)

При клике идет запрос (с параметрами value=1&idTopic=5837&%24family[name]=hash), а в ответ json массив — который обрабатывается и стрелка перерисовывается.

Таким образом, нам нужно 3 элемента: текущая страница, php бэкенд, принимающий запрос и отдающий ответ и скрипт, отправляющий запрос и обрабатывающий ответ.

Как это сделать?


Итак, пишем простейший сниппет для получения запроса:

Сниппет Ajax_test


<?php
// Откликаться будет ТОЛЬКО на ajax запросы
if ($_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') {return;}

// сниппет будет обрабатывать не один вид запросов, поэтому работать будем по запрашиваемому действию
$action = $_POST['action'];

// Если в массиве POST нет действия - выход
if (empty($action)) {return;}

//А если есть - работаем
$res = '';
switch ($action) {
  case 'helloWorld': $res = 'Hello World!'; break;
  // А вот сюда потом добавлять новые методы
}

// Если у нас есть, что отдать на запрос - отдаем и прерываем работу парсера MODX
if (!empty($res)) {
  die($res);
}
?>


Тут все очевидно: сниппет ловит ajax запросы (а мы все делаем через jquery, который любезно шлет серверу заголовок HTTP_X_REQUESTED_WITH), проверяет, задано ли действие и пытается дать для него ответ.

Пару слов о прерывании парсера. Этот метод я придумал сам, и мне кажется, он гораздо удобнее, нежели использование отдельного php файла.

Лично мне нравится, что мои сниппеты в зависимости от запроса могут работать и через ajax, и просто при обычной загрузке, не завися ни от кого.

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

В общем, решайте сами, как вам больше нравится, статья моя — пишу как делаю я.

Страница и скрипт


Конечно, мы будем использовать jQuery. Скрипт просто в теле страницы.

<!-- Наш сниппет, при обычной загрузке он ничего не делает -->
[[!Ajax_test]]

<!-- Подключаем jquery с сервера Яндекса -->
<script type="text/javascript" src="http://yandex.st/jquery/1.7.1/jquery.min.js"></script>
<!-- Наш скрипт запроса и обработки -->
<script type="text/javascript">
$(document).ready(function() {
  // Вешаем обработчик события "клик" на все ссылки с классом ajax_link
  $('a.ajax_link').click(function() {
    // Берем действие из атрибута data-action ссылки
    var action = $(this).data('action');
    // Ajax запрос к текущей страницы (а на ней наш сниппет) методом post
    $.post(document.location.href, {action: action}, function(data) {
      // Выдаем ответ
      alert('Запрос успешно выполнен')
      $('#result').html(data);
    })
    // Не даем ссылке кликнуться - нам же не нужна перезагрузка страницы?
    return false;
  })
})
</script>

<!-- Ссылка с нужным классом и data - атрибутом, с действием -->
<a href="#" data-action="helloWorld" class="ajax_link">Привет, мир!</a>

<!-- html элемент для вставки ответа от php -->
<div id="result"></div>


Итог


Всего несколько строчек кода и вы можете использовать асинхронные запросы.

Дальше нужно набивать новые методы в сниппет (вызов wayfinder, например), расставлять ссылки по страницам и развешивать обработчики событий на них.

Можно добавить к ссылке новый атрибут data-update="" и писать туда имя элемента для обновления. А при получении ответа вставлять его не в #result, как железно забито сейчас, а в указанный элемент. Тогда вы одними ссылками можете сделать сайт полностью на ajax.

Чуть изменить js скрипт и ссылку попробуйте сами.

Надеюсь, теперь, принцип ясен. Как обычно, ссылочка проверить.

У меня на страничке добавлена вторая ссылка, с другим data-action, а в сниппете добавлен второй метод ответа:
case 'showMyIp': $res = 'Ваш ip: ' . $_SERVER['REMOTE_ADDR']; break;
  • +32
  • 24 января 2012, 14:10
  • bezumkin

Комментарии (32)

RSS свернуть / развернуть
0
Большое спасибо за наглядность, а сделать что бы адреса через # менялись можно? и как если можно?
avatar

SurRealistik

  • 24 января 2012, 14:35
+12
Адреса не меняются через # (это называется якорь, кстати).

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

Важная особенность якорей — при их изменении в адресной строке скриптом, страница не перезагружается. А если вы попытаетесь изменить не адрес, а якорь — страница обновиться на новый адрес.

Именно поэтому якоря используют для hash навигации.

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

Типа вот так:

// Сохраняем при клике действие в hash
$(document).ready(function() {
  $('.ajax_link').click(function() {
    var action = $(this).data('action')
    document.location.hash = 'a=' + action;
  })
})

// А при загрузке страницы проверяем, и если что-то есть в hash - что-то делаем
var hash = document.location.hash;
if (hash != '') {
	action = hash.substr(4);
	alert(action)
}


На самом деле, все немного сложнее и индивидуальнее. Для hash навигации нужно хорошенько понимать как что работает. Когда поймете — вопросов не останется =)
avatar

bezumkin

  • 24 января 2012, 14:47
0
Спасибо за столь подробный ответ. Т.е. получается что hash можно использовать как альтернативу GET запросов + получаем данные без перезагрузки страницы?
В таком случае мы можем делать стандартные макеты, а не те, в которых только контент и загружать страницы асинхронно?
avatar

SurRealistik

  • 24 января 2012, 14:53
+11
Вы путаете теплое с мягким.

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

Это происходит оттого, что ответы ajax меняют текущую, загруженную страницу. А при обновлении вам сервер выдаст страницу без изменений.

Выходит, что вам нужно получить от сервера страницу и сразу ее изменить? А как изменить? Данные изменений можно хранить на сервере, в сессии — и это будет работать.

Но как тогда дать ссылку на измененную страницу? Вот тут то и нужна hash навигация. Чтобы данные изменения страницы хранить в строке адреса, в якоре.

Таким образом, hash это не альтернатива get-запросам, это место хранения состояния страницы, в нашем случае.

Вот пример

То есть, это просто примочка сбоку для ajax. Сама по себе она ничего не может.
avatar

bezumkin

  • 24 января 2012, 15:00
0
понял, спасибо
avatar

SurRealistik

  • 24 января 2012, 15:09
0
Идея с прерыванием парсера замечательная, действительно удобно использовать.
avatar

Ximbo

  • 24 января 2012, 18:10
+1
я тоже себе эту строчку вынес в блокнот, которую ещё раньше @bezumkin указывал в топиках
if ($_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') {return;}

Спасибо, да идея отличная!
avatar

valikras

  • 24 января 2012, 18:14
0
Лучше пользоваться jQuery или подобными. Эта строчка не обрабатывает все варианты. Подробнее в Ajax для профессионалов. Там итоговые конструкции просто монструозные. В отличие от $.post
avatar

argnist

  • 25 января 2012, 14:36
+2
Вы не поняли, что это на php заголовок запроса проверяется?

Именно jquery такой заголовок и шлет при $.ajax, $.post и $.get
avatar

bezumkin

  • 25 января 2012, 14:40
0
А как вызывать TV в сниппете в шаблоне?
avatar

FRo_OG

  • 24 января 2012, 18:18
0
я плохо формулирую вопросы :)
[[+tv.Catalog__Название]]

[[+tv.Catalog__Название]]
как то так?
avatar

FRo_OG

  • 24 января 2012, 18:22
0
я плохо формулирую вопросы :)

<div class="name"><a href="[[~[[+id]]]]" title="[[+pagetitle]]">[[+tv.Catalog__Название]]</a></div>

как то так:

<div class="name"><a href="#" title="[[+pagetitle]]" class="ajax_link">[[+tv.Catalog__Название]]</a></div>

?
avatar

FRo_OG

  • 24 января 2012, 18:24
0
лажу написал полную по ходу…
avatar

FRo_OG

  • 24 января 2012, 18:26
+11
Откройте уже для себя rtfm.modx.com

Пример доставания всех tv для ресурса с id 1 на Revolution.

if ($res = $modx->getObject('modResource', 1)) {
  $tvs = $res->getMany('TemplateVars');
  foreach ($tvs as $tv) {
    print_r($tv->toArray());
  }
}
avatar

bezumkin

  • 24 января 2012, 18:44
0
я бы в этой ситуации getMany не использовал.
$tvs = $modx->getObject('modTemplateVarResource',array('contentid'=>1));
avatar

valikras

  • 24 января 2012, 19:07
+11
вернее не getObject — а getCollection
$tvs = $modx->getCollection('modTemplateVarResource',array('contentid'=>1));

getObject — можно использовать, если мы укажим к примеру id TV.
$tvs = $modx->getObject('modTemplateVarResource',array('tmplvarid'=>$idtv, 'contentid'=>$id));
avatar

valikras

  • 24 января 2012, 19:11
0
Я привожу пример выборки всех ТВ конкретного ресурса.

Именно getObject + getMany.

bobsguides.com/revolution-objects.html
avatar

bezumkin

  • 24 января 2012, 19:15
+11
Я привожу пример выборки всех ТВ конкретного ресурса./blockquote>
я тоже конкретно ресурса с id = 1
$tvs = $modx->getCollection('modTemplateVarResource',array('contentid'=>1));

Если нету необходимости в получения данных о ресурсе (pagetitle, content....) не вижу смысла делать через одно место а потом ещё getMany использовать, если же необходимы даные о ресурсе и о TV, тогда логично — только в один массив нужно загнать.

Я не сказал, что Ваш вариант не верен, я показал, как лучше сделать. Хотя всё зависит от задачи. Лады прекращаю…
avatar

valikras

  • 24 января 2012, 19:41
+3
Я человеку показываю простой пример.

Не мастер-класс ловких методик, а простой и понятный пример оперирования MODX Api.
avatar

bezumkin

  • 24 января 2012, 20:00
0
А для Evo это будет работать?
avatar

w3d

  • 25 января 2012, 11:46
+2
Это общий принцип работы ajax.

Будет работать ВЕЗДЕ!!!
avatar

bezumkin

  • 25 января 2012, 13:30
0
Почему-то в результат закидывает копию всей страницы :(
Что не так делаю?
avatar

w3d

  • 25 января 2012, 20:52
0
Не прерываете работу парсера и он рендерит вам всю страницу.

Исправьте в конце сниппета
if (!empty($res)) {
  die($res);
}

на
die($res);
avatar

bezumkin

  • 25 января 2012, 21:01
0
Не помогает.
Evo 1.0.5
avatar

w3d

  • 25 января 2012, 21:47
0
дайте логин\пароль от админки в личку
avatar

bezumkin

  • 25 января 2012, 21:59
0
в личке
avatar

w3d

  • 25 января 2012, 22:07
+2
=)

У Эво сниппет должен быть вставлен вот так:
[!Ajax_test!]
а не
[[!Ajax_test]]
avatar

bezumkin

  • 25 января 2012, 22:31
0
Спасибо! Что-то я переработал :)
avatar

w3d

  • 26 января 2012, 10:44
0
При использовании прерывания парсера есть один нюанс. А именно — хотя полная страница не генерируется, но до момента прерывания парсер выполняет свою работу, а значит все сниппеты, которые расположены «раньше/выше» по шаблону, выполняются. Хотя их результат, разумеется, не выводится. Например

[[!Snippet]]
...
[[!Ajax]]

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

Ximbo

  • 26 января 2012, 11:11
0
Да, все верно. Поэтому у меня обычно только один сниппет на странице, а всякие вспомогательные — в шаблоне.

Ну а уж счетчики страницы так и просто в чанке футера.
avatar

bezumkin

  • 26 января 2012, 12:39
-1
«Дальше нужно набивать новые методы в сниппет (вызов wayfinder, например), расставлять ссылки по страницам и развешивать обработчики событий на них.» можете привести пожалуйста пример как это сделать?
и вот про это более подробно «Можно добавить к ссылке новый атрибут data-update=»" и писать туда имя элемента для обновления. А при получении ответа вставлять его не в #result, как железно забито сейчас, а в указанный элемент. Тогда вы одними ссылками можете сделать сайт полностью на ajax."
avatar

c4az

  • 23 февраля 2012, 01:34
0
Вы комментарий не можете понятно написать, рано вам за Ajax браться.
avatar

bezumkin

  • 23 февраля 2012, 08:42

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.