Архив за Октябрь 2008

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

В одной из предыдущих заметок я писал про то, как организовать удобный просмотр изображений на сайте. Lightbox использует библиотеки Prototype и Scriptaculous. Сегодня рассмотрим плагин FancyBox для библиотеки jQuery. Плагин позволяет

  • просматривать отдельные изображения;
  • организовать просмотр группы фотографий (фотогалерея);
  • спрятать за одной миниатюрой целую галерею фотографий;
  • выводить контент, находящийся на странице — например видеоролики с YouTube;
  • выводить произвольный контент в плавающем фрейме iframe.

Для начала нам потребуется подключить в разделе head HTML-страницы четыре JavaScript-файла:

  • jquery.js — библиотека jQuery;
  • jquery.fancybox.js — плагин FancyBox;
  • jquery.pngFix.js — позволит устранить проблему использования png-файлов в IE;
  • jquery.metadata.js — позволит использовать атрибуты class для передачи параметров плагину.

Первые два из них обязательны, а без двух остальных можно обойтись.

<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.fancybox.js"></script>
<script type="text/javascript" src="js/jquery.pngFix.js"></script>
<script type="text/javascript" src="js/jquery.metadata.js"></script>

Далее необходимо подключить стиль отображения для окна просмотра:

<link rel="stylesheet" href="css/fancy.css" type="text/css" media="screen">

Добавляем на страницу картинку-миниатюру, заключенную в тэг <a>, который ссылается на полноразмерное изображение. Заголовок, если таковой требуется, можно разместить в атрибуте title.

<a href="image_big.jpg" title="my caption"><img src="image_small.jpg" alt=""/></a>

Если необходимо разместить несколько логически связанных изображений и организовать навигацию между ними в окне просмотра, то нужно добавить атрибут rel для каждого связанного изображения. Например:

<p id="gallery">
<a href="/example/fancybox/04.jpg" rel="fancy-tour">
<img src="/example/fancybox/04-thumb.jpg" />
</a>
<a href="/example/fancybox/05.jpg" rel="fancy-tour">
<img src="/example/fancybox/05-thumb.jpg" />
</a>
<a href="/example/fancybox/06.jpg" rel="fancy-tour">
<img src="/example/fancybox/06-thumb.jpg" />
</a>
</p>

На странице может быть размещено несколько фотогалерей и каждая из них может содержать любое число изображений.

Теперь JavaScript-код:

$(document).ready(function(){
  $("#gallery a").fancybox();
});

Мы отбираем все нужные нам элементы <a> и передаем этот набор плагину FancyBox, который проделывает всю остальную работу. Можно использовать дополнительные опции:

  • hideOnContentClick — скрывать контент по клику на полноразмерном изображении. Может принимать значения true или false. По умолчанию false.
  • zoomSpeedIn — указывается скорость эффекта (в миллисекундах) при открытии полноразмерного изображения. По умолчанию — 0, т.е. без эффекта.
  • zoomSpeedOut — указывается скорость эффекта (в миллисекундах) при закрытии полноразмерного изображения. По умолчанию — 0, т.е. без эффекта.
  • overlayShow — показывать или нет дополнительный слой, который “затемняет” основное содержимое страницы. Может принимать значения true или false. По умолчанию false.
  • overlayOpacity — прозрачность для overlayShow, если конечно он true. Изменяется от 0 до 1.
  • frameWidth — определяет ширину контейнера, если выводится iframe и inline содержимое (см. пример).
  • frameHeight — определяет высоту контейнера, если выводится iframe и inline содержимое (см. пример).
  • itemLoadCallback — определяет пользовательскую функцию, которая выбирает группу фотографий для отображения (см. пример).

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

HTML:

<a id="custom" href="javascript:;"><img src="/example/fancybox/07-thumb.jpg" alt="" /></a>

JavaScript:

$("a#custom").fancybox({
  'itemLoadCallback': getGroupItems
});

var imageList = [
  {url: "/example/fancybox/07-1.jpg", title: "Первая картинка"},
  {url: "/example/fancybox/07-2.jpg", title: "Вторая картинка"},
  {url: "/example/fancybox/07-3.jpg", title: "Третья картинка"}
];

function getGroupItems(opts) {
  jQuery.each(imageList, function(i, val) {
    opts.itemArray.push(val);
  });
}

Здесь мы создаем массив imageList, каждым элементом которого является объект, состоящий из пар ключ-значение:

  • url — URL полноразмерного изображения;
  • title — содержит комментарий к изображению.

Разберем ещё, как вывести в галерею некий скрытый контент, находящийся на странице. В качестве примера используем видеоролик с YouTube.

HTML:

<a href="#testube" id="video" class="{frameWidth: 425, frameHeight: 355}"><img src="images/08-thumb.jpg"  alt="" /></a>

<div style="display:none" id="testube">
<object width="425" height="344">
<param name="movie" value="http://www.youtube.com/v/...&hl=en&fs=1"></param>
<param name="allowFullScreen" value="true"></param>
<param name="wmode" value="transparent"></param>
<embed src="http://www.youtube.com/v/...&hl=en&fs=1" type="application/x-shockwave-flash" allowfullscreen="true" wmode="transparent" width="425" height="344"></embed>
</object>
</div>

JavaScript:

$("a#video").fancybox({
  zoomSpeedIn: 0,
  zoomSpeedOut:0,
  frameWidth: 425,
  frameHeight: 344
});

И последнее — вывод любого контента через iframe.

HTML:

<a id="frame" href="http://www.google.ru/"><img src="/example/fancybox/09-thumb.jpg" alt="" /></a>

JavaScript:

$("a#frame").fancybox({
  zoomSpeedIn: 0,
  zoomSpeedOut:0,
  frameWidth: 800,
  frameHeight: 600
});

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

Создание резервной копии БД MySQL

Таблицы типа MyISAM являются платформо-независимыми, и их можно перемещать с одного сервера на другой независимо от версии сервера и операционной системы, под управлением которой он работает.

В каталоге данных для каждой базы данных заводится свой подкаталог, а каждая таблица представлена тремя файлами, имена которых совпадают с именами таблицы, а расширения имеют следующий смысл:

  • frm — определяет структуры таблицы, имена полей, их типы, параметры таблицы и т.п.;
  • MYD — содержит данные таблицы (расширение образовано от сокращения MYData);
  • MYI — содержит индексную информацию (расширение образовано от сокращения MYIndex).

Однако копирование данных непосредственно из каталога данных работающего сервера может привести к повреждению копий таблиц, причем это может случиться даже в том случае, если MySQL-сервер не обращался к копируемым таблицам в текущий момент. Поэтому перед копированием бинарных данных необходимо либо остановить сервер, либо блокировать таблицы на запись. Блокировку таблиц удобно осуществлять при помощи оператора FLUSH TABLES:

FLUSH TABLES WITH READ LOCK;

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

Для того, чтобы снять блокировку на запись, следует выполнить запрос

UNLOCK TABLES;

В дистрибутив MySQL входит скрипт горячего копирования бинарных файлов баз данных mysqlhotcopy. Данный скрипт действует по той схеме, что описана ранее: блокирует таблицы базы данных base и копирует их бинарное представление по указанному пути /to/new/path:

mysqlhotcopy base /to/new/path

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

Плагин 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";
}
?>