Архив за Сентябрь 2008

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

Название DOM (Document Object Model — объектная модель документа) говорит само за себя. DOM является способом моделирования HTML-документов. В рамках этой модели обеспечивается возможность доступа, навигации и манипулирования HTML-документами. DOM обеспечивает полный контроль над документом, предоставляя полный доступ и позволяя модифицировать содержимое документа.

Для того, чтобы наглядно представить модель DOM для Web-страницы, лучше всего воспользоваться инспектором DOM (DOM Inspector), входящим в состав браузеров Mozilla. В объектной модели документа все элементы страницы располагаются в виде древовидной иерархии.

Каждый дескриптор HTML является узлом такого дерева и связан с соответствующими подузлами и родительскими узлами. Кроме того, у каждого текстового блока имеется свой узел DOM, а точнее, текстовой узел.

Разумеется, в интерфейсе DOM API имеются методы не только для доступа, но и для добавления и удаления отдельных элементов иерархического дерева DOM. Это дает возможность изменить на странице практически все, что угодно.

Доступ к конкретным элементам

Идеальный способ доступа к отдельному элементу на странице во время работы с DOM — присвоить ему уникальный идентификационный номер (ID). В таком случае метод document.getElementById() предоставляет доступ к заданному элементу, после чего можно видоизменить этот элемент, присоединить к нему дочерние элементы или переместиться дальше по дереву DOM.

В приведенном ниже фрагменте кода осуществляется доступ к элементу <p>. Строковое представление класса этого элемента может меняться в зависимости от типа браузера. Так, если MS IE выводит лишь информацию [object], то браузеры Mozilla более многословны, предоставляя больше сведений об искомом элементе: [object HTMLParagraphElement].

<script type="text/javascript">
window.onload = function() {
  window.alert(document.getElementById("para"));
}
</script>
<p id="para">JavaScript Phrasebook</p>

Доступ по имени дескриптора

Другой способ доступа к отдельным элементам на текущей странице — по именам дескрипторов.

Когда приходится работать с рядом элементов, обозначенных одними и теми же дескрипторами HTML (например, со всеми элементами списка или же со всеми абзацами), рекомендуется использовать метод document.getElementsByTagName(). Достаточно указать имя дескриптора, чтобы получить массив всех элементов для их последующей обработки.

В приведенном ниже фрагменте кода осуществляется доступ ко всем элементам <p> и последующий их подсчет.

<script type="text/javascript">
window.onload = function() {
  window.alert(
    document.getElementsByTagName("p") +
    " (" + document.getElementsByTagName("p").length + " elements)");
}
</script>
<p>JavaScript Phrasebook</p>
<p>Sams Publishing</p>

Результат выполнения приведенного выше кода следующий: [object HTMLCollection] (2 elements), т.е. два искомых элемента. Опять же браузер MS IE предоставляет меньше информации, выводя лишь следующую строку: [object] (2 elements).

Отправка письма с вложением. Часть 1

Для отправки почтового сообщения создадим HTML-форму, состоящую из трех текстовых полей для имени автора name, его e-mail адреса email, темы сообщения subject, текстовой области message для ввода содержимого письма и поля типа file для выбора файла отправки.

Письмо будем отправлять по адресу администратора сайта $admin = ‘admin@mail.ru’

<?php
session_start();
$admin = 'admin@mail.ru';

if ( isset( $_POST['sendMail'] ) ) {
  $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();
  }

  if ( !empty( $_FILES['file']['tmp_name'] ) and $_FILES['file']['error'] == 0 ) {
    $filepath = $_FILES['file']['tmp_name'];
    $filename = $_FILES['file']['name'];
  } else {
    $filepath = '';
    $filename = '';
  }
 
  $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;
 
  if ( send_mail($admin, $body, $email, $filepath, $filename) )
    $_SESSION['success'] = true;
  else
    $_SESSION['success'] = false;
  header( 'Location: '.$_SERVER['PHP_SELF'] );
  die();
}

// Вспомогательная функция для отправки почтового сообщения с вложением
function send_mail($admin, $body, $email, $filepath, $filename)
{
  $subject = '=?windows-1251?B?'.base64_encode('Заполнена форма на сайте').'?=';
  $boundary = "--".md5(uniqid(time())); // генерируем разделитель
  $headers = "From: ".strtoupper($_SERVER['SERVER_NAME'])." <".$email.">\r\n";   
  $headers .= "Return-path: <".$email.">\r\n";
  $headers .= "MIME-Version: 1.0\r\n";
  $headers .="Content-Type: multipart/mixed; boundary=\"".$boundary."\"\r\n";
  $multipart = "--".$boundary."\r\n";
  $multipart .= "Content-type: text/plain; charset=\"windows-1251\"\r\n";
  $multipart .= "Content-Transfer-Encoding: quoted-printable\r\n\r\n";

  $body = quoted_printable_encode( $body )."\r\n\r\n";
 
  $multipart .= $body;
 
  $file = '';
  if ( !empty( $filepath ) ) {
    $fp = fopen($filepath, "r");
    if ( $fp ) {
      $content = fread($fp, filesize($filepath));
      fclose($fp);
      $file .= "--".$boundary."\r\n";
      $file .= "Content-Type: application/octet-stream\r\n";
      $file .= "Content-Transfer-Encoding: base64\r\n";
      $file .= "Content-Disposition: attachment; filename=\"".$filename."\"\r\n\r\n";
      $file .= chunk_split(base64_encode($content))."\r\n";
    }
  }
  $multipart .= $file."--".$boundary."--\r\n";

  if( mail($admin, $subject, $multipart, $headers) )
    return true;
  else
    return false;
}

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 ) );
}
?>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Отправить письмо</title>
<link rel="StyleSheet" type="text/css" href="/style/screen.css">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
</head>
<body>

<?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" enctype="multipart/form-data">
<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>Файл:</td><td><input type="file" name="file" /></td></tr>
<tr><td>&nbsp;</td><td><input type="submit" name="sendMail" value="Отправить" /></td></tr>
</table>
</form>

</body>
</html>

Письмо, содержащее вложенный файл, несколько отличается от простого. Одна из особенностей - наличие заголовка MIME-Version: 1.0. Этот заголовок указывает стандарт, которому соответствует тело сообщения.

Если мы хотим отослать письмо с прикрепленным файлом, то необходимо использовать заголовок Content-type: multipart/mixed, который обозначает, что письмо состоит из нескольких частей, каждая из которых содержит свой заголовок Content-type.

Для обозначения границы этих частей необходимо использовать параметр boundary, который еще называется маркером границы. Значением этого параметра может служить любая строка. Но надо учесть, что она должна быть уникальной и не встречалась в теле письма. Иначе письмо может быть неправильно разбито на части.

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

Для каждой части необходимо установить свои заголовки. После заголовков необходимо поставить два знака перевода строки.

Если у нас идет часть с текстом, то заголовоку Content-Transfer-Encoding надо присвоить значение quoted-printable, либо 7bit, либо 8bit. Для части с файлом этот заголовок должен быть равен base64.

Заголовок Content-Disposition, присутствующий во второй части, указывает, как почтовой программе следует отобразить данную часть письма. Он может принимать значение attachment (этот участок не является частью письма, а просто прикреплен к нему в виде файла) и inline (включение, которое используется непосредственно в письме, например, картинка, вставляемая в HTML).

В первой части заголовком Content-type: text/plain; charset=”windows-1251″ указали, что это простой текст в кодировке Windows-1251. Во второй же части заголовком Content-Type: application/octet-stream указали, что заранее не известно, какой тип файла будет отправлен.

Прикрепленный файл должен быть размещен в письме в формате base64. Преобразовать файл в этот формат можно при помощи PHP-функции base64_encode().

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

Календарь на JavaScript

Интерфейс календаря состоит из двух частей:

  1. Шапка, в которой указывается название приложения (Календарь) и осуществляется навигация между годами и месяцами. Изменение текущего года реализовано в виде двух ссылок: ссылки на предыдущий год и ссылки на последующий. В центре указывается текущий год. Аналогичным образом реализовано изменение месяца. Изменение месяца имет одну особенность. Если текущий месяц — январь, то при переходе на предыдущий месяц производится переход на декабрь предыдущего года. Аналогичным образом, если текущий месяц — декабрь, то при переходе на следующий месяц производится переход на январь следующего года. Все ссылки несут одновременно информацию о месяце и годе.
  2. Таблица календаря на один месяц. Таблица календаря имеет заголовок с названиями дней недели. Первым днем в таблице идет понедельник.

В скрипте календаря инициализируются три основных массива данных:

  • months — названия двенадцати месяцев года;
  • monthcountdays — количества дней в месяце;
  • weekdays — сокращенные названия дней недели.

Инициализация массива monthcountdays производится отдельно от массивов moths и weekdays. Предварительно необходимо определить год, для одного из месяцев которого осуществляется построение календаря, так как это необходимо для определения количества дней в феврале. Принцип определения месяца и года основан на поиске в URL текущего HTML-документа названия месяца и номера года. Если в URL название месяца или номер года отсутствуют, то календарь строится на текущие месяц и год.

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

  1. Определение текущей даты и сохранение ее значения в переменной currentdate.
  2. Инициализация массива months, содержащего названия двенадцати месяцев года, и массива weekdays, содержащего сокращенные названия дней недели.
  3. Выделение из URL HTML-документа требуемого месяца, для которого будет строиться календарь.
  4. Выделение из URL HTML-документа требуемого года, для которого будет строиться календарь.
  5. Создание объекта даты для первого числа того месяца и года, для которого строится календарь, а затем определение для этой даты дня недели, название которого сохраняется в переменной firstday.
  6. Формирование системы навигации (изменение года и месяца).
  7. Формирование таблица календаря:
    1. формирование заголовка таблицы;
    2. формирование шапки календаря с сокращенными названиями дней недели
    3. формирование первой “пустой” ячейки, если месяц начинается не с понедельника;
    4. формирование ячеек таблицы, соответствующих дням месяца;
    5. формирование последней “пустой” ячейки таблицы, если месяц не заканчивается в воскресенье.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Календарь</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
</head>
<body>
    <script type="text/javascript">
    <!--
        var i;
        var currentdate = new Date();

        var months = new Array( "Январь", "Февраль", "Март", "Апрель", "Май", "Июнь",
                       "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь" );
        var weekday = new Array( "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс" );

        var currentmonth = currentdate.getMonth();
        for(i=0;i<12;i++) {
            if( document.location.href.indexOf( encodeURIComponent(months[i]) ) != -1 ) {
                currentmonth = i;
            }
        }

        var currentyear  = currentdate.getFullYear();
        for(i=1900;i<2100;i++)
            if( document.location.href.indexOf( (new Number(i)).toString() ) != -1 )
                currentyear = i;

        var monthcountday = new Array( 31, (((currentyear%4) == 0 ) ? 29 : 28),
                                       31, 30, 31, 30, 31, 31, 30, 31, 30 ,31 );
         
        var firstday = ((new Date(currentyear,currentmonth,1)).getDay()+6)%7;

        document.write( "<h1 style='text-align:center'>Календарь</h1>"
                        + "<div align='center'>"
                       
                        + "<div><a href='calendar.html?"+encodeURIComponent(months[currentmonth])+","+(currentyear-1)+"'>"+(currentyear-1)+"</A>&nbsp;&nbsp;&nbsp;&nbsp;"
                        + "<strong>" + currentyear + "</strong>"
                        + "&nbsp;&nbsp;&nbsp;&nbsp;<a href='calendar.html?"+encodeURIComponent(months[currentmonth])+","+(currentyear+1)+"'>"+(currentyear+1)+"</a></div>"

                        + ((currentmonth!=0) ? "<div><a href='calendar.html?"+encodeURIComponent(months[currentmonth-1])+","+currentyear+"'>"+months[currentmonth-1]+"</a>&nbsp;&nbsp;&nbsp;&nbsp;"
                                             : "<div><a href='calendar.html?"+encodeURIComponent(months[11])+","+(currentyear-1)+"'>"+months[11]+" of "+(currentyear-1)+"</A>&nbsp;&nbsp;&nbsp;&nbsp;")
                        + "<strong>" + months[currentmonth] + "</strong>"
                        + ((currentmonth!=11) ? "&nbsp;&nbsp;&nbsp;&nbsp;<a href='calendar.html?"+encodeURIComponent(months[currentmonth+1])+","+currentyear+"'>"+months[currentmonth+1]+"</a></div>"
                                              : "&nbsp;&nbsp;&nbsp;&nbsp;<a href='calendar.html?"+encodeURIComponent(months[0])+","+(currentyear+1)+"'>"+months[0]+" of "+(currentyear+1)+"</a></div>")

                        + "</div>" );

        document.write ( "<table width='300' align='center' border='1' bgcolor='#EEEEEE'><tr>" );

        for(i=0;i<7;i++)
            document.write( "<th bgcolor='blue'><span style='color:white'>" + weekday[ i ] + "</span></th>" );

        if( firstday != 0 )
            document.write( "</tr><tr align='center'><td colspan='" + firstday + "'>&nbsp;</td>" );

        for(i=0;i<monthcountday[currentmonth];i++)
        {
            if((i+firstday)%7==0)
                document.write ( "</tr><tr align='center'>" );
            document.write( "<td>" + (i+1) + "</td>" );
        }

        if( (monthcountday[currentmonth]+firstday)%7 != 0 )
            document.write( "<td colspan='" + (7-((monthcountday[currentmonth]+firstday)%7)) + "'>&nbsp;</td>" );

        document.write( "</tr></table>" );
    //-->
    </script>

</body>
</html>