Warning: array_merge(): Argument #2 is not an array in /hsphere/local/home/magistr/codeismy.name/wp-content/plugins/wp-pagenavi/scb/Options.php on line 46

AJAX Чат своими руками

Дата: Июль 21, 2009

(1) комментарий

В этой статье мы рассматриваем создание AJAX приложения – чата. Пока это простая программа, но в дальнейшем мы добавим ей функциональности
Создание таблиц для чата
Для чата нам нужно создать 2 таблицы. Первая это «chat», где хранятся все комнаты чата и вторая «message» где хранятся все сообщения чата

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--Chat Table
DROP TABLE IF EXISTS `chat`;
CREATE TABLE `chat` (
`chat_id` INT(11) NOT NULL AUTO_INCREMENT,
`chat_name` VARCHAR(64) DEFAULT NULL,
`start_time` DATETIME DEFAULT NULL,
PRIMARY KEY  (`chat_id`)
) ENGINE=INNODB DEFAULT CHARSET=latin1;
--Message Table
DROP TABLE IF EXISTS `message`;
CREATE TABLE `message` (
`message_id` INT(11) NOT NULL AUTO_INCREMENT,
`chat_id` INT(11) NOT NULL DEFAULT '0',
`user_id` INT(11) NOT NULL DEFAULT '0',
`user_name` VARCHAR(64) DEFAULT NULL,
`message` TEXT,
`post_time` DATETIME DEFAULT NULL,
PRIMARY KEY  (`message_id`)
) ENGINE=INNODB DEFAULT CHARSET=latin1;


Эти таблицы – предварительная настройка и будут меняться
Хтмл основа
Начнем с заголовка. Добавим информацию о стилях, помещенную в отдельный файл. Также добавим немного яваскриптов. Потом мы сможем вынести их в отдельный файл, но пока оставим их в коде

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<head>
<style type=text/css media=screen></STYLE>
<script language=JavaScript type=text/javascript></script>
Now lets add the rest of the HTML:
AJAX Driven Web Chat.
Status: Normal
 
Current Chitter-Chatter:
<form id=frmmain name=frmmain onsubmit="">
<input id=btn_get_chat type=button value="Refresh Chat" name=btn_get_chat>
<input id=btn_reset_chat type=button value="Reset Chat" name=btn_reset_chat>
<input id=txt_message style="WIDTH: 447px" name=txt_message>
<input id=btn_send_chat type=button value=Send name=btn_send_chat>
</form

У нас есть заголовок, раздел для отображения статуса, и главный див для отображения самого чата
Также у нас есть кнопка обновить если программа затормозила, кнопка сброса для очистки всей базы, поле ввода сообщения и кнопка отправки
Еще есть пока не определенный CSS класс, и действующий стиль
Определим chat_main

1
2
3
4
5
overflow: auto;
height: 300px;
width: 500px;
background-color: #CCCCCC;
border: 1px solid #555555;

Очень полезная вещь overflow,он позволяет диву вести себя как фрейму
видим,что с FireFox все отлично работает, но требуется еще код для правильного отображения в IE, лучше использовать дивы с overflow чем фреймы потому что они лучше индексируются поисковиками
Javascript

Начнем с моего любимого куска AJAX кода

1
2
3
4
5
6
7
8
9
10
11
//Gets the browser specific XmlHttpRequest Object
function getXmlHttpRequestObject() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if(window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
} else {
document.getElementById('p_status').innerHTML 'Status: Cound not create XmlHttpRequest Object.' +
'Consider upgrading your browser.';
}
}

Код создает объект для используемых переменных, этот код можно в разном виде встретить на разных AJAX сайтах. Многие любят использовать Try..Catch блоки для проверки совместимости браузера, но имхо этот способ выглядит профессиональней
Я не уверен что это лучший из вариантов, но я знаю что Try..Catch выглядит неаккуратно
Как видишь, IE поддерживает объект ActiveX XMLHttpRequest, а FireFox, Opera и им подобные используют native. Если браузер не поддерживает что-либо из этого, мы предупреждаем пользователя в разделе статуса
Этот код можно использовать везде где нужно проверить совместимость браузера
Создадим 4 глобальных переменных, поместим их в начало скрипта

1
2
3
4
var sendReq = getXmlHttpRequestObject();
var receiveReq = getXmlHttpRequestObject();
var lastMessage = 0;
var mTimer;

 мы создали 2 обьекта, один из которых целое число и содержит последнее полученное нами сообщение, поэтому мы не отправляем весь список сообщений в каждом запросе, и переменную которая будет хранить время автообновления, поэтому можно убрать все setTimout во всех функциях
Добавим функцию для вызова последнего сообщения. Сообщения можно получать как plain text или XML, у каждого способа есть свои достоинства и недостатки. В этом AJAX приложении мы будем использовать XML

1
2
3
4
5
6
//Gets the current messages from the server
// Получаем текущее сообщение с сервера
function getChatText() {
if (receiveReq.readyState == 4 || receiveReq.readyState == 0) {
receiveReq.open("GET", 'getChat.php?chat=1&last=' + lastMessage, true);
receiveReq.onreadystatechange = handleReceive

 Сначала убедимся что наш обьект готов отправить запрос, также надо добавить код для обработки других состояний, но пока не будем этого делать
Первая строчка начинает соединение с сервисом чата который находится в’getChat.php и передает данные в строке запроса. Тип запроса GET, также можно сделать и POST, как ты увидишь через минуту
Вторая указывает какая функция javascript будет обрабатывать ответы от сервиса чата
Мы создадим эту функцию через минуту
Внутри нее мы разрешим доступ к данным, отправляемым обратно с сервера
Третья строка отправляет запрос, здесь стоит null потому что мы делаем тип запроса GET
Если был POST, здесь мы поместили бы наши параметры
А теперь создадим функцию которая обрабатывает запросы сервера

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Функция обработки возвращения текста чата
function handleReceiveChat() {
if (receiveReq.readyState == 4) {
var chat_div = document.getElementById('div_chat');
var xmldoc = receiveReq.responseXML;
var message_nodes = xmldoc.getElementsByTagName("message");
var n_messages = message_nodes.length
for (i = 0; i < n_messages; i++) {
var user_node = message_nodes[i].getElementsByTagName("user");
var text_node = message_nodes[i].getElementsByTagName("text");
var time_node = message_nodes[i].getElementsByTagName("time");
chat_div.innerHTML += user_node[0].firstChild.nodeValue + ' ';
chat_div.innerHTML += ''
chat_div.innerHTML += time_node[0].firstChild.nodeValue + '
';
chat_div.innerHTML += text_node[0].firstChild.nodeValue + '
';
lastMessage = (message_nodes[i].getAttribute('id'));
}
mTimer

Здесь много всего, поэтому рассмотрим функцию построчно
Первая строка проверяет, был ли ответ от сервера.

1
if (receiveReq.readyState == 4) {

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

1
var xmldoc = receiveReq.responseXML;

Третья создает обьект, содержащий ответ сервера

1
var message_nodes = xmldoc.getElementsByTagName("message");

Мы также можем получать данные в формате plain text через receiveReq.response.Text. Plain text лучше если возвращается одно значение, но для нескольких лучше использовать XML. С тех пор как мы будем возвращать множественные сообщения с 4 переменными в каждом запросе,XML лучший вариант. Чтобы работать с ответами, ты должен разбираться в XMLDOM. При работе с ним могут быть небольшие отличия между браузерами. Следующая строка получает все сообщения для запроса. Каджое содержит несколько частей с информацией о сообщении
Пример ответа выглядит так:

1
2
3
4
5
6
7
<ROOT>
<MESSAGE id>
<USER>Ryan Smith</USER>
<TEXT>Here is the Message Text</TEXT>
<TIME>07:53</TIME>
</MESSAGE>
</ROOT>

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

1
2
3
4
5
6
7
8
9
10
11
var n_messages = message_nodes.length
for (i = 0; i < n_messages; i++) {For every message we will all the new message to our chat screen.
var user_node = message_nodes[i].getElementsByTagName("user");
var text_node = message_nodes[i].getElementsByTagName("text");
var time_node = message_nodes[i].getElementsByTagName("time");
chat_div.innerHTML += user_node[0].firstChild.nodeValue + ' ';
chat_div.innerHTML += ''
chat_div.innerHTML += time_node[0].firstChild.nodeValue + '
';
chat_div.innerHTML += text_node[0].firstChild.nodeValue + '
'

Следующие 6 строк просто подгружают в чат данные с сервера. В конце мы храним последний полученый с сервера ID, чтобы не было повторов
lastMessage = (message_nodes[i].getAttribute(‘id’));
После того как мы обновили вывод, мы устанавливаем таймаут обновлений.
mTimer = setTimeout(‘getChatText();’,2000);
Каждые 2 секунды мы отправляем запрос к серверу, чтобы узнать, нет ли новых сообщений. Его мы храним в глобальной переменной, поэтому в дальнейшем мы можем его очистить. Отправляя запрос, нам нужно написать код для отправки сообщения на сервер. Для отправки сообщений мы будем использовать POST запрос, а не GET, потому что он позволяет отправлять на сервер большие сообщения. Создание POST запроса очень похоже на создание запроса GET
 

1
2
3
4
5
6
7
8
9
10
11
//Отправляем сообщение на сервер.
function sendChatText() {
if (sendReq.readyState == 4 || sendReq.readyState == 0) {
sendReq.open("POST", 'getChat.php?chat=1&last=' + lastMessage, true);
sendReq.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
sendReq.onreadystatechange = handleSendChat;
var param = 'message=' + document.getElementById('txt_message').value;
param += '&name=Ryan Smith';
param += '&chat=1';
sendReq.send(param);
document.getElementById('tx

Как вы видите, это очень похожий код, но после инициализации отправки(send()), мы добавляем дополнительные параметры

1
2
3
4
var param = 'message=' + document.getElementById('txt_message').value;
param += '&name=Ryan Smith';
param += '&chat=1';
sendReq.send(param);

Мы передаем три значения в наших POST данных; текст сообщения, имя пользователя (привязанный к моему имени ) и текущую комнате для дискуссий (сейчас это 1). Мы изменим их позже, чтобы учесть различные комнаты чата и имена пользователей. В этом запросе, вместо того, чтобы выполнить handleReceiveChat на возвратном вызове, мы выполним новую функцию handleSendChat. Эта функция будет довольно простой.

1
2
3
//После того как наше сообщение отосланно обновляем страницу
function handleSendChat() {
//сбрасыва

 
Все, что мы делаем здесь, убирает любой таймер обновления, который может выполниться, и затем обновить чат. Это устанавливает функциональные возможности клиентской стороны для посылки и получения сообщений чата. Последняя функция, которую мы хотим добавить прямо сейчас — возможность сбросить наше приложение чата.

1
2
3
4
5
6
7
8
9
10
11
//This cleans out the database so we can start a new chat session.
function resetChat() {
if (sendReq.readyState == 4 || sendReq.readyState == 0) {
sendReq.open("POST", 'chat/getChat.php?chat=1&last=' + lastMessage, true);
sendReq.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
sendReq.onreadystatechange = handleResetChat;
var param = 'action=reset';
sendReq.send(param);
document.getElementById('txt_message').value = '';
}
}

Мы создаем функцию, которая очень похожа на sendChatText, но вместо того, чтобы передать сообщение в параметрах POST, мы передадим параметр «action» со значением сброса. Наш возвратный вызов для запроса resetChat просто убирает любые сообщения, которые в настоящее время находятся в окне чата, и сбрасывает наш таймер обновления.

1
2
3
//Эта функция создаёт ответ после того, как страница была перегружена
function handleResetChat() {
document.getElementById('div_chat'

Мы завершим наш JavaScript, добавляя onclick обработчики к кнопкам.

1
2
3
4
5
6
7
<input id=btn_get_chat onclick=javascript:getChatText();
type=button value="Refresh Chat" name=btn_get_chat>
<input id=btn_reset_chat onclick=javascript:resetChat();
type=button value="Reset Chat" name=btn_reset_chat>
<input id=txt_message style="WIDTH: 447px" name=txt_message>
<input id=btn_send_chat onclick=javascript:sendChatText();
type=button value=Отправить name=btn_s

Вызываем функцию после каждого клика по кнопку отправить

Теперь пришло время создавать внутренности к приложению чата. У нас уже есть клиентская часть, все запросы идут в тот же самый URL. Внутреннее решает, как обработать, то что пришло с запросом. Первое , что делает наш внутренний файл, это создание нескольких HTTP заголовков для предотвращения кэширования ответа клиентским браузером

1
2
3
4
5
//Говорим браузеру, что он не должен с текущего момента использовать кэш
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT" );
header("Last-Modified: " . gmdate( "D, d M Y H:i:s" ) . "GMT" );
header("Cache-Control: no-cache, must-revalidate" );
header("Pragma: no-c

Вы можете видеть, что мы устанавливаем заголовок » Expires » в дату, которая уже прошла. Без этой строки, IE имеет тенденцию кэшировать ответ независимо от других заголовков. Другой заголовок, который мы посылаем, является Content-Type. Мы устанавливаем наш Content-Type в text/xml, таким образом браузер клиентов знает, что мы посылаем скорее Content-Type чем текстовый файл. Это облегчает создание объекта XMLDoc для различных типов браузера. Следующая строка включает файл, который содержит наши функции базы данных. Мне нравится объединять функции базы данных в отдельном файле в случае, если я должен изменить тип подключения базы данных (MSSQL, Oracle, и т.д.). потребуйте (‘database.php’); я объясню этот файл немного. Первое действие, которое мы выполняем, должно проверить наши переменные POST, чтобы видеть, послали ли новое сообщение в запросе.

1
2
3
4
5
//Проверим, есть ли новое сообщение.
if(isset($_POST['message']) && $_POST['message'] != '') {
$sql = "INSERT INTO message(chat_id, user_id, user_name, message, post_time) VALUES (" .
db_input($_GET['chat']) . ", 1, '" . db_input($_POST['name']) .
"', '" . db_input($_POST['message']) . "'

Если новое сообщение послали, то мы добавляем сообщение к нашей базе данных. $sql — строка, содержащая текст запроса. Мы используем для функции db_input, чтобы избежать кавычек, которые, возможно, ввел пользователь. Мы тогда выполняем текст запроса с нашей функцией db_query. Затем мы выясним, если запрос сбросить чат послали:

1
2
3
//Проверим, нет ли запроса на сброс.
if(isset($_POST['action']) && $_POST['action'] == 'reset') {
$sql = "DELETE FROM message WHERE chat_id = " . db_input($_GET[

Еще раз мы создаем наш текст запроса и выполняем его с нашей функцией db_query. Этот запрос просто удаляет все сообщения из базы данных, которые относятся к текущему разговору. Теперь пришло время создавать ответ, который будет отправлять данные обратно пользователю. После того, как мы закончили обновлять базу данных с любыми новыми сообщениями, посланными пользователем, мы получим список любых новых сообщений, что пользователь еще не получил. Мы запускаем, создавая наш заголовок XML и тэг открытия элемента документа.

1
2
//Создаём ответ в XML формате
$xml = '<?xml vers

Важно отметить, что не должно быть никакого пробела или других символов перед нашим объявлением XML, иначе мы столкнемся с обработкой ошибок, пытаясь анализировать XMLDom на стороне клиента. Затем, мы проверим, есть ли пользователь в комнате. Если нет, мы пошлем пользователю сообщение, что они должны ввести комнату чата прежде, чем они смогут получить любые сообщения чата.

1
2
3
4
5
6
7
8
//Проверяем действительно ли юзер в чате
if(!isset($_GET['chat'])) {
$xml .='Your are not currently in a chat session.  Enter a chat session here';
$xml .= '<MESSAGE id=0>';
$xml .= '<USER>Admin</USER>';
$xml .= '<TEXT>Your are not currently in a chat session. ';
$xml .= '<a href="">Enter a chat session here</a></TEXT>';
$xml .= '<TIME>' . date('h:i') .

 
У нас есть пустая ссылка в этом сообщении, ее мы можем позже заменить на URL к нашей странице выбора комнаты для дискуссий, но это выходит за рамки этой обучающей программы. Если пользователь будет в комнате для дискуссий, то мы получим каждое сообщение, что пользователь еще не получил и добавляет их к нашему ответу XML.

1
2
3
4
5
6
7
8
9
10
11
12
13
} else {
$last = (isset($_GET['last']) && $_GET['last'] != '') ? $_GET['last'] : 0;
$sql = "SELECT message_id, user_name, message, date_format(post_time, '%h:%i') as post_time" .
" FROM message WHERE chat_id = " . db_input($_GET['chat']) . " AND message_id > " . $last;
$message_query = db_query($sql);
while($message_array = db_fetch_array($message_query)) {
$xml .= '<MESSAGE id="' . $message_array['message_id'] . '">';
$xml .= '<USER>' . htmlspecialchars($message_array['user_name']) . '</USER>';
$xml .= '<TEXT>' . htmlspecialchars($message_array['message']) . '</TEXT>';
$xml .= '<TIME>' . $message_array['post_time'] . '</TIME>';
$xml .= '</MESSAGE>';
}
}

 
Первый шаг здесь — проверка, получил ли пользователь какие-нибудь сообщения уже. Если запрос не определял последний полученный запрос, то мы заставляем переменную $last обнулять, чтобы получить каждое предыдущее сообщение для этого разговора Затем, мы создаем нашего оператора SQL, чтобы получить все сообщение для текущего разговора, которые были зарегистрированы начиная с нашего последнего обновления. Ограничивая наш запрос только сообщениями, что мы еще не получили, мы уменьшаем количество трафика, который мы должны послать. После того, как мы выполнили наш запрос, мы зацикливаем его через каждую строку сообщения и добавляем узел сообщения к нашему XML. Каждый узел сообщения содержит номер сообщения,имя пользователя, отправившего его и время отправки. Наконец, мы закрываем наш элемент документа XML и добавляем наш XML к ответу.

1
2
$xml .= '</ROOT>';
echo $xml;

Теперь мы можем проверить работу чата. Убедитесь, что Вы создали таблицы базы данных и установили правильное значение соединения с базой в database.php

Дополнения для удобства использования

Мы можем видеть, что у нас есть наполовину рабочее управляемое приложение чата. Чтобы завершить обучающую программу, мы сделаем несколько маленького суммирования к HTML, чтобы сделать это немного более дружественным к пользователю. Одна из первых вещей, на которые Вы вероятно обратите внимание, — то, что, когда Вы печатаете сообщение и нажимаете, вводят, страница обновляется и Ваше сообщение не добавлено. Вы должны нажать кнопку «Send», чтобы добавить Ваше сообщение к чату. Чтобы устранить это, мы добавим простую функцию JavaScript. Эта функция вызовет нашу sendChatText функцию и возвратит ложь, чтобы препятствовать подтверждению формы.

1
2
//Эти функции обрабатывают нажатие ввода.
 //Чтобы препятствовать подтверждениюь формы, мы отправим сооб

Это добавит наше сообщение к чату каждый раз, когда мы вводим сообщение и нажимаем Enter. Вы также обратите внимание, что, когда Вы сначала открываете страницу, чат не обновляется. Мы создадим функцию JavaScript, которая будет вызвана, когда страница загружается, чтобы обработать инициализацию нашего приложения.
Когда документ загружается, мы запускаем обновление сообщения чата, getChatText () функцию.

1
2
3
4
//Инициализация страницы
function startChat() {
//Установка точки входа для контейнера с сообщениями
document.getElementById('txt_

Дополнительно мы устанавливаем курсор в поле ввода так что пользователь может сразу печатать сообщение. Это завершает первую часть обучающей программы. В следующей статье мы увидим, как создать множественные комнаты с различными именами пользователей. Мы также изучим другие способы расширить наше приложение чата, чтобы сделать его более устойчивым и дружественным к пользователю. Мы надеемся, эта обучающая программа дала Вам хорошую подготовку в создании AJAX приложений

перевод статьи JaWaReZ

a

    Автор: Sergey

    , , ,




    Один комментарий на "AJAX Чат своими руками"

    Verst сказал:
    21.07.2009

    Спасибо, жду продолжения

    Продолжение дискуссии на форуме: link


    Вы можете продолжить обсуждение этой статьи на форуме


    Имя : 
    Почта : 
    Сайт : 
    Комментарий : 

    Проверка комментариев включена. Прежде чем Ваши комментарии будут опубликованы пройдет какое-то время.

    Создание сайта - Echo-group Раскрутка сайтов