среда, 29 декабря 2010 г.

Очистка input file

Для загрузки файла на сервер, обычно, используют <input type="file">. Это удобный и проверенный способ, но в нём есть одна проблема - очистка этого поля от выбранного файла. Её нужно делать, если загрузка не обязательна. Т.е. у пользователя должна быть возможность отменить загрузку выбранного им файла.

Обычная очистка значения на <input type="file"> никак не повлияет, поэтому придётся прибегнуть к разным хитростям. В большинстве статей на эту тему предлагается следующее решение:

<form id="fMain" method="post" enctype="multipart/form-data" action="myAction.php">
   <div id="dFile">
      <input type="file">
      <input type="button" value="Очистить" onclick="ClearFile('dFile');" />
   </div>
</form>

function ClearFile(idFileContainer)
{
   var container = document.getElemetnById(idFileContainer);
   //Разметка элементов перезаписывается и текущее значение исчезает
   container.innerHTML = container.innerHTML;
}

Такой подход выполняет поставленную задачу. Однако он обладает одной очень неприятной особенностью. Если пользователь очистит файловое поле, а потом опять выберет файл, то этот файл не отправится на сервер (точную причину такого поведения я не знаю). Это очень серьёзная ошибка в логике работы файлового поля, поэтому я предлагаю менее красивый, но правильно работающий способ:

<form id="fMain" method="post" enctype="multipart/form-data" action="myAction.php">
   <input type="file">
   <input type="button" value="Очистить" onclick="ClearFile('fMain');" />
</form>

function ClearFile(idForm)
{
   var form = document.getElementById(idForm);
   //Массив значений всех элементов формы 
   var values = new Array(form.elements.length);

   //Запись значений всех элементов формы 
   for (var i = 0; i < form.elements.length; i++) 
   {
      values[i] = form.elements.item(i).value;
   }
   

   form.reset(); //Сброс значений всех элементов формы 

   //Восстановление значений всех элементов формы, кроме input file 
   for (var i = 0; i < form.elements.length; i++)
   {
      //Здесь сравнивается тип, т.к. используется один input file
      //Если элементов input file больше, то нужно использовать id
      if(form.elements.item(i).type != 'file')
      {
         form.elements.item(i).value = values[i];
      }
   }
}

Приходится сбрасывать все значения в форме, т.к. сброс значения для отдельного элемента не предусмотрен. Способ громоздкий и текст в форме один раз мигает при перезаписи значений. Зато вышеописанная проблема исчезает. Если у кого-то есть идея получше, то пишите о ней в комментариях обязательно.

воскресенье, 12 декабря 2010 г.

Показ анимации во время выполнения AJAX-запроса

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

В этом примере роль анимации будет исполнять gif-изображение. Если нет возможности создать gif-анимацию самостоятельно, то её можно скачать. Например, с preloaders.net. Для показа анимации нужно создать какой-нибудь элемент рядом с элементом, к которому относится выполняемый запрос:

<table>
   <tr>
      <td><input type="button" value="Отправить"></td>
      <td id="myAnimation" height="16" width="16"></td><!--Элемент для показа анимации-->
   </tr>
</table>

Показ и скрытие анимации в функции, отправляющей запрос:

function MyFunction()
{
   //Анимацию нужно показать перед отправкой запроса
   var tdAnimation = document.getElementById('myAnimation');
   tdAnimation.style.background = 'url(load.gif)';

   if(request.readyState == 4 || request.readyState == 0)
   {
      request.open("GET", "script.php", true);
      request.onreadystatechange = function()
      {
         if(request.readyState == 4)
         {
            if(request.status == 200)
            {
               //Обработка данных 
               //...

               //Скрывать анимацию нужно только после выполнения запроса
               tdAnimation.style.background = null;
            }
         }
      };
      request.send(null);
   }
   else
   {
      setTimeout('MyFunction()', 500);
   }
}

Всё очень просто. Главное показывать и скрывать анимацию в нужное время.

Оформление ссылок в виде кнопок

Если на сайте есть ссылка, то лучше всего (особенно для поисковых систем) сделать её, используя тег <a>. Однако это не всегда уместно с точки зрения оформления. Например, меню, чаще всего, оформляют в виде прямоугольных блоков (кнопок), в которых можно кликнуть не только на текст, но и на сам блок. Кроме того эти блоки, обычно, подсвечиваются при наведении на них указателя мыши.

Есть множество способов создать подобное оформление, но большая их часть довольно неудобна в реализации. В последнее время я стал пользоваться самым простым и эффективным, на мой взгляд, решением - оформлением тега <a> в виде блока через CSS. В файл со стилями добавляем следующий класс:

.menu
{
   display: block; /*Даёт возможность назначать размеры элементу*/
   width: 100px;
   padding: 4px;
   margin-bottom: 2px;
  
   border: 1px solid #000;
   background: #DDD;
   color: #000;
   text-decoration: none;
}

.menu:hover /*Для подсветки при наведении мыши*/
{
   background: #EEE; 
}

Применяем это к ссылкам:

<a class="menu" href="http://www.google.ru">Google</a>
<a class="menu" href="http://www.yandex.ru">Яндекс</a>  

Результат:

GoogleЯндекс
Описанный способ не даёт полной свободы в оформлении (хотя CSS 3 в этом поможет), но это, возможно, единственный его недостаток. Преимущества данного способа:

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

- Отлично (одинаково) работает во всех современных браузерах.

- Максимально прост в использовании.

- Очень удобно вносить изменения в оформление.

- Можно не использовать изображения.

суббота, 4 декабря 2010 г.

Подключение файлов в PHP

PHP-программисты часто не придают значения способу подключения файлов к своему скрипту. Однако разницу между ними важно знать, чтобы не сделать ошибку:

require - включает текст из файла в скрипт до его выполнения.

require_once - работает так же, как require, и проверяет - не подключается ли этот файл повторно. Повторное подключение может вызвать ошибку.

include - включает текст из файла в скрипт во время его выполнения.

include_once - работает так же, как include, и проверяет - не подключается ли этот файл повторно. Повторное подключение может вызвать ошибку.

Чаще всего (случаи статического подключения файлов), нужно использовать require_once. Например, когда подключается шапка или подвал сайта, скрипт соединения с базой данных, файл с дополнительными функциями и т.п. Использование require_once даёт защиту от ошибок при повторном подключении файла, свободу выбора места вызова в коде (т.к. подключение происходит до выполнения скрипта) и некоторый выигрыш в производительности.

Использовать include и include_once следует в тех случаях, когда использование require и require_once невозможно. Например, если путь подключения файла находится посредством выполнения скрипа (динамическое подключение файлов), а не задаётся в тексте напрямую.

Уменьшение изображения в PHP

Многие начинающие PHP-разработчики задаются вопросом - как уменьшить изображение, переданное пользователем? Это очень просто, поэтому приведённая ниже функция будет не только пропорционально уменьшать изображение, но и обрезать края, выходящие за рамки заданного соотношения сторон. Также будет сохранён альфа-канал (прозрачность) изображения, если он в нём присутствовал.

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

//$image - исходное изображение, созданное функцией imagecreatefrompng или подобными
//$width - ширина уменьшенного изображения
//$height - высота уменьшенного изображения
function GetResampledImage($image, $width, $height)
{
   //Сохраняем длину и ширину исходного изображения
   $widthOriginal = imagesx($image);
   $heightOriginal = imagesy($image);

   //Объявляем переменные для размеров пропорционально сжатого изображения (без обрезки)
   //Изначально приравниваем их к размерам уменьшенного изображения
   $widthSampled = $width ;
   $heightSampled = $height;

   //Вычисляем коэффициенты соотношения сторон уменьшенного и исходного изображений
   $aspect = $height / (float)$width;
   $aspectOriginal = $heightOriginal / (float)$widthOriginal;

   //Вычисляем соотношение коэффициентов
   $aspectAspect = $aspect / $aspectOriginal;

   //Объявляем переменные для смещения изображения (нужно для обрезки)
   $dX = 0;
   $dY = 0;

   //Вычисляем размеры пропорционально сжатого изображения (без обрезки) и смещение изображения
   if($aspectAspect > 1)
   {
      $widthSampled *= $aspectAspect;
      $dX = ($widthSampled - $width) / 2;
   }
   else
   {
      $heightSampled /= $aspectAspect;
      $dY = ($heightSampled - $height) / 2;
   }


   //Создаём прозрачное изображение для вывода уменьшенного изображения
   $imageResult = imagecreatetruecolor($width, $height);
   imagesavealpha($imageResult, true);
   imagefill($imageResult, 0, 0, imagecolorallocatealpha($imageResult, 0, 0, 0, 127));
  
   //Создаём прозрачное изображение для хранения пропорционально сжатого изображения
   $imageSampled = imagecreatetruecolor($widthSampled, $heightSampled);
   imagesavealpha($imageSampled, true);
   imagefill($imageSampled, 0, 0, imagecolorallocatealpha($imageSampled, 0, 0, 0, 127));

   //Пропорционально сжимаем исходное изображение
   imagecopyresampled($imageSampled, $image, 0, 0, 0, 0, $widthSampled, $heightSampled, $widthOriginal, $heightOriginal);

   //Обрезаем пропорционально сжатое изображение 
   imagecopy($imageResult, $imageSampled, 0, 0, $dX, $dY, $widthSampled, $heightSampled);

   return $imageResult;
}

Полученное изображение можно сохранить с помощью функции imagepng() или подобных ей.

пятница, 12 ноября 2010 г.

Что-то с датой в JavaScript

Недавно столкнулся с одной особенностью JavaScript, а точнее класса Date. Во всех известных мне языках программирования (и других языках) 01-01-2000 - это первое января двухтысячного года. В JavaScript - это первое февраля двухтысячного года.

Чтобы получить первое января нужно создавать объект Date так: new Date(2000, 0, 1). Соответственно от остальных месяцев тоже нужно отнять единицу, т.к. в конструкторе класса Date они нумеруются с 0 по 11.

суббота, 6 ноября 2010 г.

Осторожно, динамическая типизация JavaScript

Недавно возникла такая проблема - все браузеры, кроме Internet Explorer (чудеса случаются), получая параметра от элемента, считали этот параметр строкой, а не числом. Это происходило примерно в следующей ситуации:

for (var i = 0; i < objects.length; i++) //objects - строки таблицы, например
{
   objects[i].setAttribute('i', i); //i сейчас точно является числом
}

...

for (var i = 0; i < objects.length; i++) //objects - те же строки таблицы
{
   value[i] = objects[i].getAttribute('i') + 1; //что окажется в value[i]?
}

Internet Explorer в value[i] запишет 1, 2, 3 и т.д. Другие браузеры запишут туда 01, 11, 21 и т.д. Вроде, результат очевиден: к выражению содержащему только цифры прибавляется число в явном виде - должно получиться число. Однако это понимает только Internet Explorer.

Если возникнет подобный случай, сразу используйте функцию parseInt(objects[i].getAttribute('i')).

пятница, 5 ноября 2010 г.

Забавные комментарии в коде

//Mr. Compiler, please do not read this.

last = first; /* Biblical reference */

long long ago; /* in a galaxy far far away */

//Comment this later

//I am not sure if we need this, but too scared to delete.

$this->getSelect()->where('main_table.product_id = -1');//Mom, Dad... sorry

/* Logger */
private Logger logger = Logger.getLogger();
  
Комментарии взяты с сайта www.remobred.ru.

четверг, 4 ноября 2010 г.

Очень краткий курс AJAX

Основная идея AJAX - отправка запроса на сервер и получения ответа от него без перезагрузки текущей страницы. AJAX поддерживается всеми современными браузерами, поэтому устанавливать какие-либо дополнительные компоненты не потребуется. Если вы знаете JavaScript и PHP (или любой другой серверный язык), то можете считать, что уже на 90% знаете AJAX. Если не знаете, то изучайте.

Решим типичную для AJAX задачу - реализуем на сайте часы, показывающие время на сервере. Для этого при загрузке страницы нам нужно создать специальный объект JavaSctipt, который будет отправлять запрос на сервер и получать ответ от него:

var request = null; //Cпециальный объект
if (window.XMLHttpRequest)
{
   request = new XMLHttpRequest(); //Для всех браузеров, кроме IE
} 
else if (window.ActiveXObject) 
{
   request = new ActiveXObject("Microsoft.XMLHTTP"); //Для IE
}

UpdateTime(); //Запускаем обновление времени

function UpdateTime()
{
   //Проверяем готовность к отправке запроса
   if(request.readyState == 4 || request.readyState == 0)
   {
      //Указывает метод передачи, путь к скрипту и будет ли запрос выполнятся асинхронно
      request.open("GET", "script.php", true);

      //Отключаем кэширование результатов (для IE)
      request.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");

      //Назначаем функцию, обрабатывающую ответ от сервера
      request.onreadystatechange = function()
      {
         //Проверяем - получен ли ответ
         if(request.readyState == 4)
         {
            if(request.status == 200)
            {
               //Записываем ответ от сервера в div
               document.getElementById('divTimer').innerHTML = request.responseText;

               //Повторяем запрос через 500 миллисекунд
               setTimeout('UpdateTime()', 500);
            }
         }
      };

      //Отправляем запрос
      request.send(null);
   }
   else
   {
      //Если специальный объект был занят, повторяем запрос через 500 миллисекунд
      setTimeout('UpdateTime()', 500);
   }
}

В файле script.php получаем время:

<?php echo date('H-i-s'); ?>

На этом курс окончен.) Ещё раз хочу подчеркнуть - не важно какой язык используется на стороне сервера. В request.responseText окажется то, что вы увидите, введя адрес своего скрипта в браузер (например, www.site.ru/script.php).

среда, 3 ноября 2010 г.

Mozilla - баг div c размерами 100% и overflow auto

Если вам повезло, то вы не столкнулись с некорректным поведением браузера Mozilla Firefox по отношению к элементам с размерами 100% и overflow auto. А мне не повезло. Рассмотрим, что именно происходит:


На рисунке показано, что в Mozilla div увеличился и вышел за пределы страницы, полоса прокрутки в нём не появилась. В остальных браузерах элемент отображается правильно. Код страницы выглядит так:

<body>
   <table width="100%" height="100%">
      <tr>
         <td></td>
         <td width="300">
            <div id="d1" style="width: 100%; height: 100%; overflow: auto; border: solid;">
               Большой текст или большой дочерний элемент.
            </div>
         </td>
      </tr>
   </table>
</body>

Я долго искал решение в CSS, но не нашёл. Поэтому пришлось решать проблему через JavaScript. В моём случае было несколько div с обозначенной проблемой, поэтому код написан для любого количества элементов:

//Массив с id элементов, размер которых нужно контролировать
var resizes = new Array("d1", "d2", "d3");

//Функция для правильного изменения размеров
function AutoSize()
{
   if(IsMozilla())
   { 
      //Два цикла нужны для корректного изменения размеров
      //В противном случае элементы растягивают друг друга
      for (var i = 0; i < resizes.length; i++)
      {
         var obj = document.getElementById(resizes[i]);
         obj.style.height = '1px';
      }

      for (var i = 0; i < resizes.length; i++)
      {
         var obj = document.getElementById(resizes[i]);
         obj.style.height = obj.parentNode.offsetHeight;
      }
   }
}

//Функция для определения того, что сайт запущен в Mozilla
function IsMozilla()
{
   var brow = navigator.userAgent.toLowerCase();
   return (brow.indexOf("gecko") >= 0 && brow.indexOf("chrome") < 0  && brow.indexOf("safari") < 0);
}

Функцию AutoSize нужно вызвать при загрузке страницы и изменении её размера:

<body onLoad="AutoSize();" onResize="AutoSize();">

Пока с этим кодом у меня никаких проблем не было. Всё исправно работает.

вторник, 2 ноября 2010 г.

Проект write2know - поиск совместными усилиями

Сидел я как-то вечером за своим новым ноутбуком - спокойно работал над сайтом. Вдруг, звонит мне директор нашей компании и спрашивает - знаю ли я Delphi. Большую часть времени я программировал на C# и Delphi не знаю, но он всё-равно сказал мне прийти на совещание в начале недели.

На совещании говорилось о созданиии программы для обсуждения поисковых запросов. Ничего не поняли? И я сразу не понял. Суть проекта была в следующем: пользователь вводит запрос, например, в Google, а другие пользователи видят этот запрос и могут комментировать его. Т.о. люди могут найти в комментариях то, чего не нашли в поисковике, или просто посмеяться.

Сначала я сделал приложение на C++ (которого тоже совсем не знал), т.к. требовалась максимальная кроссплатформенность, а .NET установлен далеко не у всех пользователей (Windows XP живее всех живых). Но такая реализация оказалась не совсем удобной. Поэтому пришлось ещё прилично поработать и сделать AJAX-сайт.


Сейчас сайт доступен по адресу write2know.ru.
Мои записи и на Я.ру — levelost!