Все уже научились создавать xmlHttp объекты, посылать запросы стороннему файлу, получать, обрабатывать и выводить XML данные. Кто не в теме, прошу сюда. Теперь, на базе этого, мы с вами рассмотрим конкретный пример – всемирно известный ShoutBox!

WTF?

ShoutBox (англ. to shout – кричать, box – блок, коробка) или SayBox, TagBoard, ChatterBox – можно отнести к интерактивным «приколам», которые имеют большую популярность среди домашних страничек, игровых порталов и т.д.. Суть данного блока в том, что посетитель, зайдя на сайт, может, без всяких регистраций, «крикнуть» что-нибудь в «толпу». Интересно ведь иногда, что люди выкрикивают, а самые яркие и забавные можно коллекционировать.

Некоторые называют их банально - «оставьте своё сообщение», другие с фантазией – «крик-о-мания» - личное дело каждого, но принцип работы у всех один.

Типичные ShoutBox’ы хранят сообщение последнего крикнувшего, другие - 5-10 последних сообщений. Не знаю как вы, но я нечасто встречал таких блоков в рунете. В англоязычной сфере ShoutBox’ы получили распространение с появлением блогов.

Итак, что нам предстоит сделать:

  • Таблицу в базе данных для хранения всех криков – кто как привык, но в рамках статьи будем подключаться к MySQL;
  • Серверный скрипт выдачи криков – опять же на ваше усмотрение, я выбрал php. Выдачу будем производить в формате XML;
  • Сам блок ShoutBox – с приёмом и передачей криков;
  • Javascript функции – для того, чтобы связать ShoutBox со скриптом выдачи криков (мы ведь AJAXим, помните?);

Итак, приступим.

База данных

В новой таблице создадим следующие поля:

  • id – уникальный номер записи (unique, auto_increment)
  • name – имя или ник автора
  • message – крикнутое сообщение
  • timestamp – время крика (поле типа INT(11) которое будет хранить дату и время в формате UNIX, кто не знаком – погуглите)

Запрос:

CREATE TABLE `contents` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `message` varchar(255) NOT NULL,
  `timestamp` int(11) NOT NULL DEFAULT 'UNIX_TIMESTAMP()',
  UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM;

Текстовая информация в AJAX передается всегда в кодировке utf-8, но хранить мы в базе все будем в windows-1251, так что полученные данные в php скрипте нам придется переводить (это касается только ввода информации). Многие скажут, что windows-1251 это прошлый век, но я уважаю майкрософт и к этому привык ;) (по крайней мере live.com на 1251 написан). Если вам удобнее работать с utf-8 то пожалуйста - вас никто не заставляет… Только не забудьте убрать конвертирование в php скрипте ;)

Крико-двигатель

Сперва взглянем на готовый скрипт, а затем всё по порядку:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
  $db = mysql_connect('localhost', 'root', '');
  mysql_select_db('shoutbox');
 
  mysql_query("SET CHARACTER SET cp1251");
 
  header('Content-type: application/xml; charset=windows-1251');
  header('Cache-Control: no-cache');
 
  echo '<?xml version="1.0" encoding="windows-1251"?>' . "\n";
  echo "<shoutbox>\n";
 
  if (isset($_POST["name"], $_POST["message"]))
  {
    $name = iconv("UTF-8", "windows-1251", $_POST["name"]);
    $message = iconv("UTF-8", "windows-1251", $_POST["message"]);
    $sql = "INSERT INTO `contents` (`name`, `message`, `timestamp`) VALUES ('$name', '$message', UNIX_TIMESTAMP())";
    mysql_query($sql,$db);
?>
    <status>200</status>
<?php
  }
  else
  {
    $sql = "SELECT * FROM `contents` ORDER BY `timestamp` DESC LIMIT 5";
    $rs = mysql_query($sql,$db);
    while ($row = mysql_fetch_array($rs))
    {
?>
    <entry>
      <id><?=$row["id"];?></id>
      <name><?=$row["name"];?></name>
      <message><?=$row["message"];?></message>
      <timestamp><?=date("H:i", $row["timestamp"]);?></timestamp>
    </entry>
<?php
    }
  }
?>
</shoutbox>

Скрипт отвечает за то, чтобы записывать данные в нашу базу данных а так же за вывод требуемой информации в XML формате.

$db = mysql_connect('localhost', 'root', '');
mysql_select_db('shoutbox');
 
mysql_query("SET CHARACTER SET cp1251");
 
header('Content-type: application/xml; charset=windows-1251');
header('Cache-Control: no-cache');

Первые две команды – создать соединение с базой данных, и выбрать базу данных shoutbox для работы. Следом мы отправляем запрос в базу о том, что мы будем читать и писать в кодировке windows-1251 (в юниксе известна как cp1251). Заголовки выдачи сервера тоже немало важны, особенно Cache-control. Здесь мы говорим браузерам, что не стоит кэшировать данный документ, так как он постоянно меняется.

echo '<?xml version="1.0" encoding="windows-1251"?>' . "\n";
echo "<shoutbox>\n";

Далее, мы начинаем формировать наш будущий XML документ – стандартный XML заголовок (опять же с указанием кодировки), и «корневой» элемент – shoutbox (в прошлом уроке он назывался у нас newsarchive). Обратите внимание на \n после каждой строки – это переход на следующую строку, и немало важный элемент в XML, так как некоторые браузеры без переходов по строкам не воспринимают XML файл как «валидный», т.е. как правильный.

Кстати, может возникнуть вопрос, почему бы нам не написать это за пределами php. Всё очень просто – php.exe (обработчик .php файлов) воспринимает текст внутри тэгов <?php (или <?) и ?>, поэтому он попытается обработать данную строку, так как она начинается с <?, а строка <?xml для него – сплошная чушь.

Следом – две ветки. Первый случай (присутствуют $_POST[“name”] и $_POST[“message”]) – когда требуется записать данные в базу, второй – выдать 5 последних записей из базы.

Рассмотрим по порядку:

if (isset($_POST["name"], $_POST["message"]))
{
  $name = iconv("UTF-8", "windows-1251", $_POST["name"]);
  $message = iconv("UTF-8", "windows-1251", $_POST["message"]);
  $sql = "INSERT INTO `contents` (`name`, `message`, `timestamp`) VALUES ('$name', '$message', UNIX_TIMESTAMP())";
  mysql_query($sql,$db);
?>
  <status>200</status>
<?php
  }

Здесь мы извлекаем данные из переменных $_POST[“name”] и $_POST[“message”], а так же переводим их из кодировки utf-8 в windows-1251 (функция iconv). Формируем запрос на добавление в базу, выполняем его, и в XML возвращаем 200 в ветке status. Здесь можно будет добавить проверки на взлом, спам и т.д., и вместо 200 возвращать, например, 300. А при получении 300 в нашем JS файле выдавать сообщение об ошибке.

Далее, выдача 5 последних записей:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
else
{
  $sql = "SELECT * FROM `contents` ORDER BY `timestamp` DESC LIMIT 5";
  $rs = mysql_query($sql,$db);
  while ($row = mysql_fetch_array($rs))
  {
?>
  <entry>
    <id><?=$row["id"];?></id>
    <name><?=$row["name"];?></name>
    <message><?=$row["message"];?></message>
    <timestamp><?=date("H:i", $row["timestamp"]);?></timestamp>
  </entry>
<?php
  }

Здесь все просто – выбираем последних 5 записей из базы и выводим соответственно в XML формате, в элемент entry с подэлементами id, name, message, timestamp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
  <title>ShoutBox</title>
  <script src="shout.js" type="text/javascript"></script>
</head>
<body onload="shout_read();">
  <div id="shoutbox">
  </div>
  <form onsubmit="shout(); return false;">
    <input type="text" id="name" value="имя" maxlength="10">
    <input type="text" id="message" value="message">
    <input type="image" id="go" src="blank.png">
  </form>
</body>
</html>

Вопросов, надеюсь, не возникнет. Shout.js будет содержать наши функции. При загрузке страницы вызываем функцию shout_read() – считать данные. Элемент div с идентификатором shoutbox – то, куда будем выводить полученные сообщения. В нашей форме, при отправки (onsubmit) – вызываем функцию shout(); а так же return false; для того чтобы форма никуда не вела (особенно любит это делать IE). Два текстовых поля – имя и сообщение, а так же картинка. Зачем картинка-то? При нажатии на кнопку Enter событие onsubmit автоматически срабатывает. В IE она не срабатывает, если нет кнопки Submit или картинки, так что поместим туда картинку размером 1х1 белую.

AJAX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
var shouting = false;
 
function getXmlHttp() {
  var xmlhttp;
  try {
    xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) {
      try {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (E) {
      xmlhttp = false;
    }
  }
 
  if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
    xmlhttp = new XMLHttpRequest();
  }
  return xmlhttp;
}
 
function shout() {
  var xmlHttp;
  xmlHttp = getXmlHttp();
 
  inName = document.getElementById("name").value;
  inMessage = document.getElementById("message").value;
 
  if (inName.length < 1 || inMessage.length < 1)
  {
    alert('Сообщение или ник не могут быть пустыми!');
    return false;
  }
 
  document.getElementById("message").value = "";
  document.getElementById("message").focus();
 
  var httpParams = "name=" + inName + "&message=" + inMessage;
  xmlHttp.onreadystatechange = function() {
    if (xmlHttp.readyState == 4)
    {
      var xmlDoc = xmlHttp.responseXML.documentElement.getElementsByTagName("status");
      if (xmlDoc[0].firstChild.nodeValue == 200) // всё окей
      {
        shouting = true;
        shout_read();
      }
    }
  };
 
  xmlHttp.open('POST', 'shout.php', true);
  xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded;');
  xmlHttp.setRequestHeader('Content-length', httpParams.length);
  xmlHttp.setRequestHeader('Connection','close');
  xmlHttp.send(httpParams);
}
 
function shout_read() {
  var xmlHttp;
  xmlHttp = getXmlHttp();
 
  xmlHttp.onreadystatechange = function() {
    if (xmlHttp.readyState == 4)
    {
      obj = document.getElementById("shoutbox");
      obj.innerHTML = "";
 
      var xmlDoc = xmlHttp.responseXML.documentElement.getElementsByTagName("entry");
      for (i = xmlDoc.length-1; i >= 0 ; i--)
      {
        var new_el = document.createElement("div");
        new_el.innerHTML = '<b>'+xmlDoc[i].getElementsByTagName("name")[0].firstChild.nodeValue+':</b> '+xmlDoc[i].getElementsByTagName("message")[0].firstChild.nodeValue;
        obj.appendChild(new_el);
      }
      if (!shouting)
        setTimeout(shout_read,5000);
      else
        shouting = false;
      }
    }
 
  xmlHttp.open('GET', 'shout.php', true);
  xmlHttp.send(null);
}

Сперва объясню, зачем здесь булевая переменная shouting. При вызове функции shout_read(), после получения данных, создается таймер, который эту же функцию вызовет через 5 секунд. Что-то вроде автоматического обновления блока. Наша HTML форма вызывает эту функцию сразу же при загрузки страницы. Функция shout() тоже вызывает shout_read() после отправки данных, то есть у нас уже будет два таймера по 5 секунд. Если отправить сообщение 5 раз, то у нас будет 6 таймеров. То есть у нас будет работать 6 таймеров одновременно, что будет посылать 6 запросов на обновление каждые 5 секунд. А представьте, что будет если мы напишем 20 сообщений!

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

С функцией getXmlHttp() мы уже знакомы из предыдущего урока.

Функция shout(), как уже было сказано выше, отправляет сообщение серверному скрипту. Рассмотрим каждый ее кусок:

1
2
3
4
5
6
7
8
9
10
11
inName = document.getElementById("name").value;
inMessage = document.getElementById("message").value;
 
if (inName.length < 1 || inMessage.length < 1)
{
  alert('Сообщение или ник не могут быть пустыми!');
  return false;
}
 
document.getElementById("message").value = "";
document.getElementById("message").focus();

Первые две строки получают значения полей name и message (имя и сообщение). Далее идет проверка на отсутствие значений. То есть мы не можем послать пустое сообщение или сообщение без имени – выводим соответствующее сообщение. Следующие две строки – обнуление поля сообщения и установление в него фокус (IE устанавливает фокус на картинку).

Далее формируем параметры и слушатель:

1
2
3
4
5
6
7
8
9
10
11
12
var httpParams = "name=" + inName + "&message=" + inMessage;
xmlHttp.onreadystatechange = function() {
  if (xmlHttp.readyState == 4)
  {
    var xmlDoc = xmlHttp.responseXML.documentElement.getElementsByTagName("status");
    if (xmlDoc[0].firstChild.nodeValue == 200)
    {
      shouting = true;
      shout_read();
    }
  }
};

Для передачи данных, мы будем использовать метод POST. Формируется он так же как и GET (имя=значение&имя2=значение2 …) но передаются в зашифрованном виде после отправки самого запроса. Далее, в уже известном нам блоке, читаем что написал нам серверный скрипт в ветке status (200, помните). Если все окей, то устанавливаем переменную shouting = true и вызываем функцию shout_read() для чтения данных.

Ну и в конце формируем собственно запрос, и отправляем его серверному скрипту:

1
2
3
4
5
xmlHttp.open('POST', 'shout.php', true);
xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded;');
xmlHttp.setRequestHeader('Content-length', httpParams.length);
xmlHttp.setRequestHeader('Connection','close');
xmlHttp.send(httpParams);

С первой строкой вы уже знакомы, только метод поменялся – POST. Далее устанавливаем передаваемые заголовки. Content-type – тип передаваемого контента, application/x-www-form-urlencoded. Указываем, что мы будем передавать зашифрованные данные. Accept-Charset – кодировка получаемых данных. Content-length – длинна передаваемых данных, ее можно получить из свойства length переменной httpParams. Connection close означает закрытие заголовков и подготавливает серверный скрипт к приему данных. Сами данные передаются параметром к функции send() объекта xmlHttp.

Функция чтения:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function shout_read() {
  var xmlHttp;
  xmlHttp = getXmlHttp();
 
  xmlHttp.onreadystatechange = function() {
    if (xmlHttp.readyState == 4)
    {
      obj = document.getElementById("shoutbox");
      obj.innerHTML = "";
 
      var xmlDoc = xmlHttp.responseXML.documentElement.getElementsByTagName("entry");
      for (i = xmlDoc.length-1; i >= 0 ; i--)
      {
        var new_el = document.createElement("div");
        new_el.innerHTML = '<b>'+xmlDoc[i].getElementsByTagName("name")[0].firstChild.nodeValue+':</b> '+xmlDoc[i].getElementsByTagName("message")[0].firstChild.nodeValue;
        obj.appendChild(new_el);
      }
      if (!shouting)
        setTimeout(shout_read,5000);
      else
        shouting = false;
      }
    }
 
  xmlHttp.open('GET', 'shout.php', true);
  xmlHttp.send(null);
}

Запрос отправляется тому же серверному скрипту (shout.php) без параметров. При получении ответа обрабатываем его, и помещаем в наш элемент shoutbox, предварительно удалив все предыдущие блоки (obj.innerHTML = “”). Основной элемент у нас здесь entry. Для каждого элемента entry создаем новый элемент div, наполняем его соответствующим контентом – имя: сообщение и добавляем его в родительский элемент (shoutbox), полученный чуть выше (obj = document.getElementById(“shoutbox”)).

В предыдущем уроки обработка документа была малость иначе. Для ее сокращения мы в объект xmlDoc помещаем не весь документ, а только ту часть, которая связана с элементами entry. Далее, в цикле подставляем общее количество элементов через свойство length объекта xmlDoc и от него до 0 (в обратном порядке) выводим элементы. Обратите внимание, что length возвратит нам 5, но индекс элементов в xmlDoc начинается с 0, то есть от 0-4, потому и xmlDoc.length-1.

Ну а далее идет то, о чем писалось выше. Если таймеров не существует, то создаем – функция setTimeout.

Формирование и посылка запроса стандартные.

Ну вот собственно и все! Объясню как это работает. Открываем наш html документ – видим форму для ввода сообщений (ну и несколько сообщений, если кто уже повводил). Пишем имя, сообщение и отправляем все кнопкой enter. Здесь вступает javascript – собирает введенные данные, формирует и отправляет запрос нашему php скрипту, который эти данные заносит в базу данных. Затем, за кулисами формируется еще один запрос, в этот раз на получение данных, отправляется тому же скрипту, который, выбрав 5 последних записей (включая уже нашу новую) из базы данных и выдает их в XML документе. Javascript обрабатывает этот документ и выводит 5 сообщений, удаляя старые.

Не останавливайтесь – spice up!

Ни в коем случае не останавливайтесь на этом. Вашему ShoutBox’у нужно что-то уникальное, что отличит его от остальных аналогов. Добавляйте эффекты, смайлы, проверки на спам, в общем насколько фантазии хватит. Так же неплохо было бы иметь статистику – сколько всего криков, и можно даже архив с функцией поиска.

Полезные ссылки

Google Bookmarks Digg Reddit del.icio.us Ma.gnolia Technorati Slashdot Yahoo My Web News2.ru БобрДобр.ru RUmarkz Ваау! Memori.ru rucity.com МоёМесто.ru Mister Wong