Рубрика «AJAX»

Плагин jFrame для библиотеки jQuery

Часто на сайтах в боковой панели можно видеть разные дополнительные сервисы: голосование, обратная связь, гостевая книга и т.п. Как правило, такие скрипты написаны с использованием AJAX или размещены в плавающем фрейме. С другой стороны, в Интернете можно найти большое число PHP-скриптов на любой вкус, но написанные в традиционном стиле — без использования объекта XMLHttpRequest. Как использовать все это богатство у себя на сайте так, чтобы они:

  • во-первых, не мешали работе друг друга;
  • во-вторых, не мешали работе основного скрипта.

Поясню подробнее. Допустим, у нас на сайте есть каталог продукции: это основное содержание страницы. А боковое поле (сайдбар) содержит форму обратной связи и голосование. Все три скрипта могут отправлять данные формы. И каждый из них должен принимать только свои данные, не реагируя на остальные.

Недавно наткнулся в Сети на способ решения этой задачи — плагин jFrame для библиотеки jQuery. jFrame использует AJAX функции для загрузки контента из другого документа (скрипта) с того же домена. Т.е. он полностью повторяет возможности элемента <iframe>, разница только в том, что весь подгружаемый из другого файла контент становится полноценной частью кода документа-родителя.

Пример использования:

<html>
<head>
  <title>jFrame</title>
  <script type="text/javascript" src="jquery.js"></script>
  <script type="text/javascript" src="jquery.form.js"></script>
  <script type="text/javascript" src="jquery.jframe.js"></script>
  <script type="text/javascript">
    jQuery.fn.waitingJFrame = function () {
      $(this).html("<strong>Загрузка...</strong>");
    }
  </script>
</head>
<body>
  <div id="feedback" src="/catalog/feedback/"></div>
  <div id="voting" src="/catalog/voting/"></div>
</body>
</html>

Анекдот по теме:

Все садятся в современный супер-самолет. Из динамиков раздается:
— Уважаемые пассажиры! Наш самолет оборудован по последнему слову техники. Во время полета вам будет предложены услуги бара, кинотеатра, бассейна, сауны. Для желающих открыты библиотека, магазины, спортивный зал и другие заведения культурного отдыха.
А теперь, приведите спинки кресел в вертикальное положение, пристегните ремни, и мы попробуем вместе со всей этой фигней взлететь!

Ладно, а теперь попробуем со все этой фигней взлететь. Скрипт каталога продукции, формирующий основное содержание страницы (DOCUMENT_ROOT/catalog/index.php):

<?php
$dblocation = "localhost";   // Имя сервера
$dbuser     = "root";        // Имя пользователя
$dbpswrd    = "";            // Пароль
$dbname     = "catalog";     // Имя базы данных

// Соединение с сервером базы данных
$dblink = mysql_connect( $dblocation, $dbuser, $dbpswrd );
mysql_query( 'SET NAMES cp1251' );
// Выбираем базу данных
mysql_select_db( $dbname, $dblink );
?>

<html>
<html>
<title>Каталог продукции</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<style type="text/css">
a.modern {
  font-weight: bold;
  text-decoration: none;
}
</style>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.form.js"></script>
<script type="text/javascript" src="js/jquery.jframe.js"></script>
<script type="text/javascript">
    jQuery.fn.waitingJFrame = function () {
        $(this).html("<strong>Загрузка...</strong>");
    }
</script>
</head>
<body>

<table border="1" cellpadding="4" cellspacing="0" width="100%" height="100%">
<tr>
  <td colspan="2"><h1>ЗАО "Рога и копыта"</h1></td>
</tr>
<tr valign="top">
  <td width="70%">
  <h2>Каталог продукции</h2>

 
  <?php
  // Формируем запрос на извлечение товарных позиций
  $query = 'SELECT code, title, description, price
            FROM products
            ORDER BY title'
;
  $res = mysql_query($query);
  echo '<table border="1" cellpadding="4" cellspacing="0">';
  echo '<tr>';
  echo '<th>Код</th>';
  echo '<th>Наименование</th>';
  echo '<th>Описание</th>';
  echo '<th>Цена</th>';
  echo '</tr>';
  while( $prd = mysql_fetch_array( $res ) ) {
    echo '<tr>';
    echo '<td>'.$prd['code'].'</td>';
    echo '<td>'.$prd['title'].'</td>';
    echo '<td>'.$prd['description'].'</td>';
    echo '<td align="right">'.$prd['price'].'</td>';
    echo '</tr>';
  }
  echo '</table>';
  ?>

  </td>
  <td width="30%">
    <div id="feedback" src="/catalog/feedback/"></div>
    <div id="voting" src="/catalog/voting/"></div>
  </td>
</tr>
<tr>
  <td colspan="2" align="right">Copyright</td>
</tr>
</table>

</body>
</html>

Форма обратной связи (DOCUMENT_ROOT/catalog/feedback/index.php):

<?php
session_start();
$admin = 'admin@mail.ru';
header("Content-type: text/html; charset=windows-1251");

if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
  if ( !isset( $_POST['sendMail'] ) ) {
    $_POST['name']    = iconv('UTF-8', 'CP1251', $_POST['name']);
    $_POST['email']   = iconv('UTF-8', 'CP1251', $_POST['email']);
    $_POST['subject'] = iconv('UTF-8', 'CP1251', $_POST['subject']);
    $_POST['message'] = iconv('UTF-8', 'CP1251', $_POST['message']);
  }

  $name    = substr( $_POST['name'], 0, 64 );
  $email   = substr( $_POST['email'], 0, 64 );
  $subject = substr( $_POST['subject'], 0, 64 );
  $message = substr( $_POST['message'], 0, 250 );
 
  $error = '';
  if ( empty( $name ) ) $error = $error.'<li>Не заполнено поле "Имя"</li>';
  if ( empty( $email ) ) $error = $error.'<li>Не заполнено поле "E-mail"</li>';
  if ( empty( $subject ) ) $error = $error.'<li>Не заполнено поле "Тема"</li>';
  if ( empty( $message ) ) $error = $error.'<li>Не заполнено поле "Сообщение"</li>';
  if ( !empty( $email ) and !preg_match( "#^[0-9a-z_\-\.]+@[0-9a-z\-\.]+\.[a-z]{2,6}$#i", $email ) )
    $error = $error.'<li>поле "E-mail" должно соответствовать формату somebody@somewhere.ru</li>';
  if ( !empty( $error ) ) {
    $_SESSION['sendMailForm']['error']   = '<p>При заполнении формы были допущены ошибки:</p><ul>'.$error.'</ul>';
    $_SESSION['sendMailForm']['name']    = $name;
    $_SESSION['sendMailForm']['email']   = $email;
    $_SESSION['sendMailForm']['subject'] = $subject;
    $_SESSION['sendMailForm']['message'] = $message;
    header( 'Location: '.$_SERVER['PHP_SELF'] );
    die();
  }
 
  $body = "АВТОР:\r\n".$name."\r\n\r\n";
  $body .= "E-MAIL:\r\n".$email."\r\n\r\n";
  $body .= "ТЕМА:\r\n".$subject."\r\n\r\n";
  $body .= "СООБЩЕНИЕ:\r\n".$message;
  $body = quoted_printable_encode( $body );

  $theme   = '=?windows-1251?B?'.base64_encode('Заполнена форма на сайте').'?=';
  $headers = "From: ".$_SERVER['SERVER_NAME']." <".$email.">\r\n";
  $headers = $headers."Return-path: <".$email.">\r\n";
  $headers = $headers."Content-type: text/plain; charset=\"windows-1251\"\r\n";
  $headers = $headers."Content-Transfer-Encoding: quoted-printable\r\n\r\n";
 
  if ( mail($admin, $theme, $body, $headers) )
    $_SESSION['success'] = true;
  else
    $_SESSION['success'] = false;
  header( 'Location: '.$_SERVER['PHP_SELF'] );
  die();
}
 
function quoted_printable_encode ( $string ) {
   // rule #2, #3 (leaves space and tab characters in tact)
   $string = preg_replace_callback (
   '/[^\x21-\x3C\x3E-\x7E\x09\x20]/',
   'quoted_printable_encode_character',
   $string
   );
   $newline = "=\r\n"; // '=' + CRLF (rule #4)
   // make sure the splitting of lines does not interfere with escaped characters
   // (chunk_split fails here)
   $string = preg_replace ( '/(.{73}[^=]{0,3})/', '$1'.$newline, $string);
   return $string;
}

function quoted_printable_encode_character ( $matches ) {
   $character = $matches[0];
   return sprintf ( '=%02x', ord ( $character ) );
}

?>

<h2>Обратная связь</h2>

<?php
if ( isset( $_SESSION['sendMailForm'] ) ) {
  echo $_SESSION['sendMailForm']['error'];
  $name    = htmlspecialchars ( $_SESSION['sendMailForm']['name'] );
  $email   = htmlspecialchars ( $_SESSION['sendMailForm']['email'] );
  $subject = htmlspecialchars ( $_SESSION['sendMailForm']['subject'] );
  $message = htmlspecialchars ( $_SESSION['sendMailForm']['message'] );
  unset( $_SESSION['sendMailForm'] );
} else {
  $name    = '';
  $email   = '';
  $subject = '';
  $message = '';
}

if ( isset( $_SESSION['success'] ) ) {
  if ( $_SESSION['success'] )
    echo '<p>Письмо успешно отправлено</p>';
  else
    echo '<p>Ошибка при отправке письма</p>';
  unset( $_SESSION['success'] );
}
?>

<form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="POST">
<table>
<tr><td>Имя:</td><td><input type="text" name="name" maxlength="64" value="<?php echo $name ?>" /></td></tr>
<tr><td>E-mail:</td><td><input type="text" name="email" maxlength="64" value="<?php echo $email ?>" /></td></tr>
<tr><td>Тема:</td><td><input type="text" name="subject" maxlength="64" value="<?php echo $subject ?>" /></td></tr>
<tr><td>Сообщение:</td><td><textarea name="message" rows="5" cols="30"><?php echo $message ?></textarea></td></tr>
<tr><td>&nbsp;</td><td><input type="submit" name="sendMail" value="Отправить" /></td></tr>
</table>
</form>

Скрипт голосования (DOCUMENT_ROOT/catalog/voting/index.php):

<?php
session_start();
header("Content-type: text/html; charset=windows-1251");

$actions = array( 'showForm', 'voting', 'result' );

if ( isset( $_GET['action'] ) )
  $action = $_GET['action'];
else
  $action = 'showForm';
 
if ( !in_array( $action, $actions ) ) $action = 'showForm';

switch( $action )
{
  case 'showForm': // Показать форму для голосования
    showForm();
    break;
  case 'voting':   // Голосование
    voting();
    break;
  case 'result':   // Показать результаты голосования
    result();
    break;
}

function showForm()
{
  echo '<h2>Голосование</h2>';
  echo '<form action="'.$_SERVER['PHP_SELF'].'?action=voting" method="POST">'."\n";
  echo '<table>'."\n";
  echo '<tr><th colspan="2">Оцените сайт</th></tr>'."\n";
  echo '<tr><td><input type="radio" name="mark" value="5" checked /></td><td>Отлично</td></tr>'."\n";
  echo '<tr><td><input type="radio" name="mark" value="4" /></td><td>Хорошо</td></tr>'."\n";
  echo '<tr><td><input type="radio" name="mark" value="3" /></td><td>Средне</td></tr>'."\n";
  echo '<tr><td><input type="radio" name="mark" value="2" /></td><td>Плохо</td></tr>'."\n";
  echo '<tr><td><input type="radio" name="mark" value="1" /></td><td>Очень плохо</td></tr>'."\n";
  echo '<tr><td colspan="2" align="center"><input type="submit" value="Оценить" /></td></tr>'."\n";
  echo '</table>'."\n";
  echo '</form>'."\n";
  echo '<p><a href="'.$_SERVER['PHP_SELF'].'?action=result">Результаты голосования</a></p>'."\n";
}

function voting()
{
  if ( !isset( $_POST['mark'] ) ) {
    header( 'Location: '.$_SERVER['PHP_SELF'].'?action=showForm' );
    die();
  }
  $mark = (int)$_POST['mark'];
  if ( $mark > 5 or $mark < 1 ) {
    header( 'Location: '.$_SERVER['PHP_SELF'].'?action=showForm' );
    die();
  }
  // Если пользователь уже голосовал - не учитываем его голос
  if ( isset( $_SESSION['completed'] ) ) {
    header( 'Location: '.$_SERVER['PHP_SELF'].'?action=showForm' );
    die();
  }
  $file = file_get_contents( 'voting.txt' );
  // $result[0] - общее количество проголосовавших посетителей
  // $result[1] - количество посетителей, которые поставили оценку "1"
  // ..........
  // $result[5] - количество посетителей, которые поставили оценку "5"
  // $result[6] - средняя оценка
  $result = explode( '|', $file );
  $result[6] = ($result[6] * $result[0] + $mark) / ($result[0] + 1);
  $result[0] = $result[0] + 1;
  $result[$mark] = $result[$mark] + 1;
  if ( $fp = fopen('voting.txt', 'w') ) {
    // Ставим на файл исключительную блокировку
    if ( flock($fp, LOCK_EX) ) {
      fwrite( $fp, implode( '|', $result ) );
      flock( $fp, LOCK_UN );
    }
    fclose( $fp );
    $_SESSION['completed'] = true;
  }
  header( 'Location: '.$_SERVER['PHP_SELF'].'?action=result' );
  die();
}

function result()
{
  $file = file_get_contents( 'voting.txt' );
  $result = explode( '|', $file );
  $width = 200;
  $mark = array( 1 => 'Очень плохо', 2 => 'Плохо', 3 => 'Средне', 4 => 'Хорошо', 5 => 'Отлично' );
  echo '<h2>Результаты голосования</h2>';
  echo '<table>'."\n";
  for ( $i = 5; $i > 0; $i-- ) {
    $percent = $result[$i]/$result[0];
    $w = round($width*$percent);
    echo '<tr>';
    echo '<td>'.$mark[$i].'</td>';
    echo '<td width="'.$width.'"><img src="/catalog/voting/1.gif" width="'.$w.'" height="10" /></td>';
    echo '<td>'.$result[$i].'</td>';
    echo '</tr>'."\n";
  }
  echo '</table>'."\n";
  echo '<p>Всего проголосовало: '.$result[0].
       '<br/>Средняя оценка: '.number_format($result[6], 2, '.', '').'</p>'."\n";
  echo '<p><a href="'.$_SERVER['PHP_SELF'].'?action=showForm">Вернуться к голосованию</a></p>'."\n";
}
?>

Загрузка файлов средствами AJAX

Прежде всего, нужно сразу сказать, что отправить (загрузить) на сервер файл с помощью AJAX нельзя, поэтому приходится прибегать к некоторым хитростями, а конкретно - использовать скрытый фрейм iframe.

Итак, мы создаем скрытый фрейм iframe, и в атрибуте target тега form указываем имя этого фрейма. Отправка файла будет выполнена обычным способом. Но с точки зрения посетителя загрузка будет выглядеть асинхронной - страница, которую он видит, перезагружена не будет.

<form action="/upload.php" name="uploadForm" method="post" target="hiddenframe" enctype="multipart/form-data"
onsubmit="document.getElementById('res').innerHTML=''; document.getElementById('loading').style.display='block';
return true;"
>

<input type="file" name="userfile" />
<input type="submit" value="Загрузить"  />
</form>
<div id="res" style="margin: 1em 0"></div>
<div id="loading" style="display:none; position: absolute; z-index: 99; left: 45%; top: 45%;">
<img src="/loading.gif" border="0" /> Идет загрузка...
</div>
<iframe id="hiddenframe" name="hiddenframe" style="width:0; height:0; border:0"></iframe>

Работает это так - при нажатии на кнопку submit на экране появляется изображение “loading” (загрузка), потом идет отправка формы. Когда файл загрузился, мы убираем изображение загрузки и выводим на основную страницу сообщение с результатами.

<?php
// ...............................................
sleep(5);
echo '<script type="text/javascript">';
echo 'window.parent.document.getElementById("loading").style.display="none";';
// Если загрузка прошла успешно
if (empty($error)) {
   echo 'window.parent.document.getElementById("res").innerHTML="Файл успешно загружен";';
} else {
  echo 'window.parent.document.getElementById("res").innerHTML="Ошибка при загрузке файла";';
}
echo '</script>';
?>

Серверный скрипт upload.php создает текстовую строку, содержащую обычный JavaScript код. Когда ответ сервера будет загружен во фрейм, код будет автоматически выполнен.

Общее представление об AJAX. Часть 6

Извлечение HTTP-заголовков

Метод getAllResponseHeaders() объекта XMLHttpRequest возвращает все HTTP-заголовки, полученные от сервера, тогда как метод getResponseHeader() - только один конкретный заголовок. В слуедующем фрагменте кода демонстрируется, каким образом извлекается информация о типе используемого Web-сервера:

var XMLHttp = getXMLHttp();
XMLHttp.open("GET", "sometext.txt", true);
XMLHttp.onreadystatechange = handlerFunction;
XMLHttp.send(null);

function handlerFunction() {
  if (XMLHttp.readyState == 4) {
    var servertype = XMLHttp.getResponseHeader("Server");
    window.alert("Web server used: " + servertype);
  }
}

function getXMLHttp() {
  var XMLHttp = null;
  if (window.XMLHttpRequest) {
    try {
      XMLHttp = new XMLHttpRequest();
    } catch (e) { }
  } else if (window.ActiveXObject) {
    try {
      XMLHttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        XMLHttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e) { }
    }
  }
  return XMLHttp;
}

Получение XML-данных от сервера

Свойство responseText отлично подходит для обработки ограниченного количества неструктурированных данных. Однако более более изящный подход к обработке сложных, структурированных данных в приложении AJAX предоставляет свойство responseXML. При обращении к этому свойству будет получен ответ на запрос HTTP в виде объекта XML DOM языка JavaScript, но при условии, что сервер возвращает действительные данные XML. в противном случае будет получено пустое значение null.

Доступ к подобным данным объекта XML осуществляется таким же образом, как и к элементам DOM из кода JavaScript. Для данного примера кода используется следующий XML-файл books.xml:

<books>
  <book pubdate="2006">
    <title>JavaScript Phrasebook</title>
    <publisher>Sams Publishing</publisher>
  </book>
  <book pubdate="2006">
    <title>MySQL Phrasebook</title>
    <publisher>Sams Publishing</publisher>
  </book>
  <book pubdate="2005">
    <title>PHP Phrasebook</title>
    <publisher>Sams Publishing</publisher>
  </book>
</books>

Для того, чтобы браузеры могли прочитать этот XML-файл (особо строгими правилами в этом отношении отличается Internet Explorer), клиенту должен быть отправлен правильный тип MIME, а именно: text/xml. Этот тип указывается в свойстве mime.types файла конфигурации Apache.

С другой стороны, сценарий на сервере может сам предоставить файл с правильным типом MIME:

<?php
header('Content-type: text/xml');
readfile('books.xml');
?>

В следующем фрагменте кода осуществляется доступ к данным из XML-файла с использованием структуры DOM:

var XMLHttp = getXMLHttp();
XMLHttp.open("GET", "books.xml", true);
XMLHttp.onreadystatechange = handlerFunction;
XMLHttp.send(null);

function handlerFunction() {
  if (XMLHttp.readyState == 4) {
    var xml = XMLHttp.responseXML;
    var book = xml.getElementsByTagName("book");
    for (var i=0; i<book.length; i++) {
      var pubdate = book[i].getAttribute("pubdate");
      var title, publisher;
      for (var j=0; j<book[i].childNodes.length; j++) {
        if (book[i].childNodes[j].nodeName == "title") {
          title = book[i].childNodes[j].firstChild.nodeValue;
        } else if (book[i].childNodes[j].nodeName == "publisher") {
          publisher = book[i].childNodes[j].firstChild.nodeValue;
        }
      }
      window.alert(title + " by " + publisher + " (" + pubdate + ")");
    }
  }
}

function getXMLHttp() {
  var XMLHttp = null;
  if (window.XMLHttpRequest) {
    try {
      XMLHttp = new XMLHttpRequest();
    } catch (e) { }
  } else if (window.ActiveXObject) {
    try {
      XMLHttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        XMLHttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e) { }
    }
  }
  return XMLHttp;
}

Ссылки по теме: