Aug 23

Потихоньку подходит к концу конкурс 10K Apart. Заходишь, смотришь и думаешь - “дааа, чего только люди не впихнут в 10 килобайт JavaScript-а”. :)

Из наиболее понравившегося:

  • Fontanero - увлекательная игрушка на тему “жизнь водопроводчика”. На Хабре про неё уже писали создатели.
  • Poker Heads Up - долго играл, доиграл до конца, выиграл, понравилось. Из названия понятно, о чем игрушка. :)
  • Racer 10k - вызвавшие ностальгию по моему первому компьютеру (а это был в далеком 1995-м году первый пентиум 133МГц) гоночки. Не очень затягивает, но не написать не мог. PS. Игра работает в Chrome намного быстрее, чем в FF, что положительным образом сказывается на результатах.

written by fxposter \\ tags: ,

Jun 16

На работе столкнулись с “особенностью” работы jQuery.live(), на которую хотелось бы обратить внимание, потому как, судя по всему, отнюдь не все о ней знают (и в результате чего пишут неработающий код).

Итак, простой пример - навешивание двух событий на один и тот же элемент:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").bind("click", function(e) {
      alert("a");
      return false;
    });
    $("a").bind("click", function(e) {
      alert("b");
      return false;
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

Пример можно посмотреть здесь. В результате клика на ссылку - получаем 2 alert-а, всё хорошо, ожидаемо и предсказуемо.

Переписываем код для работы с jQuery.live(). Для тех, кто в танке - live() вешает событие не на сам элемент, а на document. В результате bubbling-а событие, которое произошло над каким-либо элементом поднимается вверх по DOM-дереву и соответственно вызывает обработчики всех элементов, которые оно встретит. Если вы и этого не знали - то вам не нужно читать мой блог, а пора идти и покупать книгу по JavaScript-у (мне, кстати, тоже давно пора, но всё никак не соберусь). Итак, в конце концов событие доходит до document-а и обработчики вызываются у него. Обработчик, который устанавливает jQuery.live() проверяет - соответствует ли event.target (а именно здесь хранится обьект DOM-дерева, с которым произошло событие) соответствующему селектору (в данном случае - это селектор “a”) и если соответствует - то выполняет обработчик.

Преимущества и недостатки - это тема отдельной статьи. Если не уклонятся в сторону оптимизации, то основным преимуществом, на мой взгляд, является тот факт, что обработчики, навешенные live()-ом будут запускаться даже для элементов, которые были динамически добавлены на страницу, в отличии от bind()-обработчиков, которые на эти элементы нужно будет навешивать вручную (если непонятно, почему это работает именно так - читаем предыдущий абзац, если все равно непонятно - идем покупать всё ту же книгу).

Далее - зачем нужен “return false” в конце обработчика? Он предотвращает от того, чтобы вызывалось действие по умолчанию (в данном случае - переход по ссылке и событие не поднималось выше). Чаще всего JS-разработчики вообще не думают о bubbling-е и под “return false” понимают только “отмену действия по умолчанию”, ну или они вообще не знают, что именно происходит и пишут “return false”, потому что так работает.

Такое отношение jQuery частенько прощает. Но не в случае с live()-методом. Попробуем запустить следующий пример:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").live("click", function(e) {
      alert("a");
      return false;
    });
    $("a").live("click", function(e) {
      alert("b");
      return false;
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

В результате клика теперь выскакивает только один alert. Пора обратится к документации:

# To stop further handlers from executing after one bound using .live(), the handler must return false. Calling .stopPropagation() will not accomplish this.

Хаха. В данном случае jQuery интерпретирует false “несколько иначе”. :)

Для того, чтобы “пофиксить” подобный баг нужно обратится все к тому же bubbling-у и обработке событий и сделать именно то, что предполагается разработчиком - “отменить действие по умолчанию”. Это делается с помощью метода event.preventDefault() (пример):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").live("click", function(e) {
      alert("a");
      e.preventDefault();
    });
    $("a").live("click", function(e) {
      alert("b");
      e.preventDefault();
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

И самое главное (барабанная дробь!) - при использовании bind() для навешивания обработчиков preventDefault() тоже можно использовать!

Наткнулись мы на эту “фичу”, когда у нас почему-то перестали вызываться некоторые обработчики

Напоследок, замечу еще одно - элемент, на который навешено хотя бы один обработчик события через bind() с “правильно работающим return false”, никогда не будет вызывать никакие live()-события. ;)

Так что будьте бдительны и не забывайте об особенностях обработки событий в JS. Удачи.

written by fxposter \\ tags: , , ,

Nov 28

На днях попросили меня помочь написать функцию сортировки массива на JavaScript, чтобы сохранилась связь между ключами и их значениями, т.е. аналог asort() в PHP. Поверхностный поиск в Google ничего подобного найти не смог, поэтому родилась вот такая функция (принимает массив или хеш, на выходе - хеш):

function asort(arr, sort_function) {
  if (sort_function == undefined)
    sort_function = function(a, b) { return a - b }

  var b = [];
  for (var i in arr)
    b.push([i, arr[i]]);
  b.sort(function(a, b) { return sort_function(a[1], b[1]) });

  c = {};
  for (var i in b)
    c[b[i][0]] = b[i][1];
  return c;
}

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

// сортировка по возрастанию
var h = asort([1, 10, 2, 5, 4]);
for(i in h)
  alert(h[i]);

// сортировка по убыванию
var j = asort([1, 10, 2, 5, 4], function(a, b) { return b - a });
for(i in j)
  alert(j[i]);

PS. В JS я не спец, так что, возможно, всё сделано абсолютно неправильно :)

written by fxposter \\ tags:

Jun 12

Блог Nihilogic в очередной раз удивил. На этот раз - шахматами на JavaScript в полном 3D с поддержкой управления как клавиатурой, так и мышкой.

Читаем. Играем.

written by fxposter \\ tags:

Jun 03

После релиза jQuery 1.2.6 долгое время раздел Release Notes пустовал. Сегодня вот заметил, что его наконец заполнили.

Из улучшений:

  • Глобальное ускорение
    • работа с событиями ускорена более чем в два раза
    • селекторы ускорены на 13%
    • .offset() ускорен на 21%
    • работа с CSS (.css()) ускорена на четверть
  • Кое-что улучшено, кое-что добавлено, вроде в API ничего не изменено

written by fxposter \\ tags: ,

Jun 03

Как сообщает нам наш официальный источник - вышел первый релиз-кандидат jQuery UI - набора виджетов и визуальных эффектов для известного в узких кругах JS-фреймворка jQuery.

Как утверждает источник, RC1 довольно стабилен, а точнее - это самая стабильная версия из тех, которые вообще выпускались. :) Так что - спешите обновляться.

Ах да, чуть не забыл - в некоторых местах поменялось API (в draggable и resizable модулях так точно поменялось), так что будьте всё же осторожны и предусмотрительны.

written by fxposter \\ tags: , ,

May 29

Авось, кто-то еще не догадался… :)

Самый простой способ использования своих скриптов совместно с Google AJAX Libraries API:

google.load("jquery", "1");

google.setOnLoadCallback(function() {
  $('<script type="text/javascript" src="_url_вашего_скрипта_"></script>').appendTo('head');
  $('<script type="text/javascript" src="_url_вашего_другого_скрипта_"></script>').appendTo('head');
  // ...
});

Аналогичным образом подключаются, например, плагины для jQuery.

written by fxposter \\ tags: , , ,

May 24

Кросспост моего поста про unobtrusive javascript на хабрахабр вызвал целую кучу критики:

  • Пока дождешься DOMContentLoaded и, соответственно, применения связей событий - пройдет куча времени
  • “Ссылки вида <a href=”javascript:saw(111)”> мне нравятся больше”
  • Производительность инлайн-вставок JS будет выше
  • и т.д.

По-моему вывод можно сделать один - многие люди не готовы принять этот подход - может, слишком сложно, может, еще что-то?.. Не знаю, не знаю..

written by fxposter \\ tags: ,

May 23

Цель статьи - показать, что из себя представляет “ненавязчивый JavaScript”, для чего он нужен, и чем он лучше “навязчивого” JavaScript. В рунете я подобных статей не встречал (может они и есть, но мне на глаза не попадались и немного погуглив, я тоже ничего не нашел), а как показывает практика - очень многие не знают, что это такое и как этим пользоваться.

Что такое Unobtrusive JavaScript

Unobtrusive JavaScript - это техника программирования на языке JavaScript, которая состоит из следующих принципов:

  • разделения структуры (HTML) / оформления (CSS) и поведения (JavaScript)
  • использование JavaScript для повышения удобства использования уже рабочего приложения
  • применения техники Graceful degradation - если браузер не поддерживает те или иные функции, которые мы добавляем в приложение с помощью JavaScript - приложение всё равно остается рабочим

Зачем?

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

Как?

Легче всего показать это на примере. За ним далеко идти не нужно - возьмем всеми любимый Хабрахабр:

<div class="text_comments"> 

  <div class="comment_item" style="margin-left: 0px;">

    <div class="service_text_comments_holder">
      <a href="http://fxposter.habrahabr.ru/" class="comments_nickname">fxposter</a>
      ...
    </div>

    <div class="comment_text">...</div>

    <div class="comments_reply">
      <div class="reply_word_holder" id="reply_link866650">(<a href="javascript:saw(866650);">ответить</a>)</div>

        <div style="display: none" id="reply866650">
        <!-- форма отправки комментария -->
      </div>

    </div>
  </div>
</div>

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

Что плохо в этом фрагменте кода?

  1. JavaScript идет вперемешку с HTML (<a href="javascript:saw(866650);">ответить</a>)
  2. У людей с отключенным JavaScript’ом ответить на комментарий не получится в принципе

Как его можно улучшить?

  1. Вынести “навешивание” событий в отдельный файл
  2. Сделать так, чтобы при отключенном JavaScript’е пользователь перебрасывался на отдельную страницу, где бы он мог ответить на выбранный комментарий

Сказано - сделано. Преобразуем HTML к следующему виду:

<div class="text_comments"> 

  <div class="comment_item" style="margin-left: 0px;">

    <div class="service_text_comments_holder">
      <a href="http://fxposter.habrahabr.ru/" class="comments_nickname">fxposter</a>
      ...
    </div>

    <div class="comment_text">...</div>

    <div class="comments_reply">
      <div class="reply_word_holder" id="reply_link866650">(<a href="reply.php?comment_id=866650" class="show_reply_form" id="show_reply_form_866650">ответить</a>)</div>

        <div style="display: none" id="reply866650">
        <!-- форма отправки комментария -->
      </div>

    </div>
  </div>
</div>

Как видите - я изменил тег <a> (присвоил ему “нормальный” href, добавил id и class). Теперь при нажатии на ссылку “ответить” пользователя будет перебрасывать на страницу ответа на выбранный вопрос. Этим я выполнил второй пункт в списке улучшений. Теперь давайте взглянем на первый пункт: для того, чтобы у пользователей, у которых включен JavaScript вместо редиректа выполнялось открытие формы под самим комментарием мне нужно выбрать все элементы с классом “show_reply_form” и каждому из них назначить на событие onclick функцию, которая бы “открывала” соответствующую форму.

Напишем соответствующую функцию:

function showForm(event) {
  var id = parseInt(this.id.replace('show_reply_form_', ''));
  saw(id);
  return false;
}

Она берет this.id (т.е. id текущего обьекта), убирает из него “фразу” “show_reply_form_“, тем самым получая номер элемента, который нам нужно открыть и вызывает функцию saw, которая присутствовала изначально в HTML-коде. Для того, чтобы не произошел редирект после клика на ссылку - функция возвращает false.

Осталось только связать эту функцию с нашими ссылками.

В jQuery это делается так:

$('.show_reply_form').click(showForm);

В PrototypeJS - так:

$$('.show_reply_form').each(function(element) {
  element.onclick = showForm;
});

После присвоения нашей функции элементу - this.id станет относится к id этого элемента (да, это “магия JavaScript” :) ).

Весь JavaScript-код теперь можно вынести в отдельный файл:

function showForm(event) {
  var id = parseInt(this.id.replace('show_reply_form_', ''));
  saw(id);
  return false;
}

window.onload = function(event) {
  $$('.show_reply_form').each(function(element) {
    element.onclick = showForm;
  });
}

Здесь мы вызываем “связывание” наших ссылок с функцией показа формы при событии window.onload (при загрузке страницы).

Таким образом я выполнил и первый пункт в списке улучшений.

Выводы

На мой взгляд, такое использование JavaScript, а именно - вынос всех функций на JS в отдельный файл и связывание этих функций с элементами страницы с помощью различных событий (здесь мы видели события window.onload и element.onclick) - это на данный момент - единственно правильное использование JavaScript.

Дерзайте, господа. :)

PS. Я прекрасно знаю, что можно использовать событие не window.onload, а DOMContentLoaded. Но я считаю, что для примера понятнее будет всё же использование window.onload.

written by fxposter \\ tags: ,

May 18

Никита организовал конкурс для JS-программистов. Задание довольно простое, у меня ушел примерно час на написание + рефакторинг. Главный приз - логотип для блога, так что если вы более-менее разбираетесь в JS - почему бы не выкроить часик и не поучаствовать?

written by fxposter \\ tags: