Показаны сообщения с ярлыком JavaScript. Показать все сообщения
Показаны сообщения с ярлыком JavaScript. Показать все сообщения

14.07.2011

Неблокирующая загрузка кнопки +1

Хотите установить на свой сайт гугловую кнопку +1? Имейте в виду, что она может негативно повлиять на скорость загрузки страниц. В частности, оригинальный сниппет содержит тег <script>, который блокирует рендеринг и загрузку последующих ресурсов на странице. К счастью, эта проблема легко решается.

<g:plusone></g:plusone> остается в том месте где должна появиться кнопка. Код неблокирующей загрузки лучше поместить перед закрывающим тегом </body>:

<script>
(function(d, t) {
    var g = d.createElement(t),
        s = d.getElementsByTagName(t)[0];
    g.async = true;
    g.src = 'https://apis.google.com/js/plusone.js';
    g.text = '{lang:"nl"}';
    s.parentNode.insertBefore(g, s);
}(document, 'script'));
</script>

Код неблокирующей загрузки написан Матиасом, пофиксен для IE мной.

UPDATE: Гугл выпустил неблокирующую версию кнопки +1.

12.02.2010

Cоздание элементов из HTML-строк в jQuery 1.4

Еще немного на тему простого шаблонирования в JavaScript

Используя jQuery, я довольно часто создаю элементы из HTML-строк с последующим добавлением атрибутов и навешиванием обработчиков событий. Примерно так:

$("<input/>")
    .attr({
        type: "text",
        name: "city"
    })
    .val("Moscow")
    .focusin(function() {
        $(this).addClass("active");
    })
    .focusout(function() {
        $(this).removeClass("active");
    })
    .appendTo("form");

В jQuery 1.4 есть приятное нововведение: вторым аргументом можно передать объект со свойствами создаваемого элемента. Код становится проще и аккуратнее:

$("<input/>", {
    type: "text",
    name: "city",
    val: "Moscow",
    focusin: function() {
        $(this).addClass("active");
    },
    focusout: function() {
        $(this).removeClass("active");
    }
}).appendTo("form");

Этот объект может содержать любые свойства понимаемые методом attr(), там же можно навесить обработчики событий, а также, не отходя от кассы, можно задать значения для методов val, css, html, text, data, width, height и offset.

Кроме того, в такой записи неплохо смотрится и более сложный шаблон с вложенным элементом:

$("<a/>", {
    href: "/test/",
    "class": "my_link",
    html: $("<img/>", {
        src: "test.gif",
        width: 60,
        height: 30
    }),
    click: function() {
        doSomething();
    }
}).appendTo("body");

27.01.2010

JavaScript Shortcuts Library

Выложил библиотеку для создания клавиатурных шорткатов JavaScript Shortcuts Library (jQuery plugin). Пригодится при разработке больших веб-приложений, а также написании игр на JavaScript.

Библиотека приятна и проста в использовании. Добавляем шорткат:

$.Shortcuts.add({
    type: 'down',
    mask: 'Ctrl+A',
    handler: function() {
        debug('Ctrl+A');
    }
});

Добавляем еще один:

$.Shortcuts.add({
    type: 'up',
    mask: 'Shift+B',
    handler: function() {
        debug('Shift+B');
    }
});

Начинаем реагировать на шорткаты:

$.Shortcuts.start();

Поддерживаются три типа событий, по которым могут срабатывать обработчики:

  1. down — на нажатие клавиши или сочетания клавиш
  2. up — на отпускание
  3. hold — на нажатие и удержание (обработчик будет вызван сразу после нажатия и потом будет вызываться с некоторой периодичностью пока нажата клавиша)

Строка задающая сочетание клавиш должна состоять из имен клавиш разделенных плюсами. Может быть не более одной клавиши отличной от Ctrl, Shift или Alt. Строка нечувствительна к регистру. Примеры: "Esc", "Shift+Up", "ctrl+a".

Кроме того, можно создавать несколько списков шорткатов, а затем, в зависимости от ситуации на странице, активизировать определенный список.

Добавляем шорткат в список "another":

$.Shortcuts.add({
    type: 'hold',
    mask: 'Shift+Up',
    handler: function() {
        debug('Shift+Up');
    },
    list: 'another'
});

Переключаемся на список шорткатов "another":

$.Shortcuts.start('another');

Также можно удалять шорткаты:

$.Shortcuts.remove({
    type: 'hold',
    mask: 'Shift+Up',
    list: 'another'
});

Останавливаем работу шорткатов (делается unbind навешенных обработчиков событий):

$.Shortcuts.stop();

Поддерживаемые клавиши:

  • Модификаторы: Ctrl, Shift, Alt
  • Цифры: 0—9
  • Буквы: A—Z (case-insensitive)
  • Специальные клавиши: Backspace, Tab, Enter, Pause, CapsLock, Esc, Space, PageUp, PageDown, End, Home, Left, Up, Right, Down, Insert, Delete, F1—F12, ? (знак вопроса), минус, плюс

Буду рад замечаниям и предложениям по улучшению.

22.01.2010

Простое шаблонирование в JavaScript

Задача: в JS на основе имеющихся данных сгенерить блок (кусок хтмл), заполнить его этими данными и вставить в DOM.

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

var data = {
    url: '/test/',
    thumb_src: 'test.gif',
    thumb_width: 60,
    thumb_height: 30,
    caption: 'Трам-парам!'
};

В итоге для этих данных хотим получить вот такой хтмл:

<div class="preview"><p class="image"><a href="/test/"><img src="test.gif" width="60" height="30"/></a></p><p class="caption">Трам-парам!</p></div>

Встают вопросы: в каком виде хранить шаблон и как впердолить в этот шаблон наши данные?

Вручную создавать элементы (createElement) и аппендить их (appendChild) довольно муторно. Получается громоздко и шаблон размазывается тонким слоем по JS-коду.

Часто делают конкатенацию кусочков шаблона вперемешку с данными:

var result = '<div class="preview"><p class="image"><a href="' + data.url + '"><img src="' + data.thumb_src + '" width="' + data.thumb_width + '" height="' + data.thumb_height + '"/></a></p><p class="caption">' + data.caption + '</p></div>';

Тоже не ахти — постоянно путаешься в этом нагромождении одинарных и двойных кавычек, к тому же хочется все-таки иметь шаблон в чистом виде.

Я использую способ, который подсмотрел в лекциях Дугласа Крокфорда. Добавляем в прототипы объекта String метод supplant, который в строке ищет выражения заключенные в фигурные скобки {}. Каждое найденное выражение используется как ключ к переданному объекту, и, если по этому ключу лежит строковое или числовое значение, то выражение в фигурных скобках заменяется этим значением.

/**
 * supplant() does variable substitution on the string. It scans
 * through the string looking for expressions enclosed in {} braces.
 * If an expression is found, use it as a key on the object,
 * and if the key has a string value or number value, it is
 * substituted for the bracket expression and it repeats. 
 */
String.prototype.supplant = function(o) {
    return this.replace(/{([^{}]*)}/g,
        function(a, b) {
            var r = o[b];
            return typeof r === 'string' || typeof r === 'number' ? r : a;
        }
    );
};

Теперь мы можем сделать так:

var template = '<div class="preview"><p class="image"><a href="{url}"><img src="{thumb_src}" width="{thumb_width}" height="{thumb_height}"/></a></p><p class="caption">{caption}</p></div>';

var result = template.supplant(data);

Еще один пример (подстановка данных в сообщение пользователю):

var template = 'Take train {number} from {from} to {to}.';

var data = {
    from: 'Madrid',
    to: 'Barcelona',
    number: '78A'
};

var result = template.supplant(data);

24.06.2009

border-radius спешит на помощь

В соавторстве с Вадимом Макишвили

Для реализации скругленных уголков мы искали максимально легкое решение — отсутствие дополнительной разметки, минимум CSS и JS. Конечно, есть очень мощные средства, например, rocon, но нам они показались избыточными и громоздкими.

Идея была в том, чтобы использовать CSS-свойство border-radius, которое поддерживают некоторые прогрессивные браузеры:

  • Firefox начиная с версии 1.0 (~24% пользователей)
  • Safari начиная с 3.0 + Chrome (~2%)
  • Konquerer (~0,05%)

Итого, для ~25% пользователей уголки можно рисовать средствами браузера — уже неплохо.

Смешно, но свойство border-radius, которое есть в CSS3, пока не поддерживается ни одним браузером. Однако, некоторые браузеры поддерживают аналогичные проприетарные свойства, например, -moz-border-radius в Firefox.

.corners {
    /* Возможно какой-нибудь браузер в будущем, будем надеяться IE */
    border-radius: 4px;
    /* WebKit (Safari/Chrome) */
    -webkit-border-radius: 4px;
    /* KHTML (Konquerer) */
    -khtml-border-radius: 4px;
    /* Возможно Opera в будущем */
    -opera-border-radius: 4px;
    /* Gecko (Firefox) */
    -moz-border-radius: 4px;
}

Для остальных браузеров, не поддерживающих border-radius, будем яваскриптом вставлять доп. элементы для реализации уголков.


// Проверяет, поддерживает ли элемент elem CSS-свойство border-radius.
var isBorderRadiusSupported = function (elem) {
    var s = elem.style;
    return typeof s.borderRadius === "string" ||
           typeof s.WebkitBorderRadius === "string" ||
           typeof s.KhtmlBorderRadius === "string" ||
           typeof s.OperaBorderRadius === "string" ||
           typeof s.MozBorderRadius === "string";
};

// Добавляет внутрь элемента elem доп. элементы реализующие уголки.
var wrapInCorners = function (elem) {
    // Реализуем уголки любым известным способом
    $(elem).prepend('<i class="lt"/><i class="rt"/><i class="lb"/><i class="rb"/>');
};

// Реализует закругленные уголки с помощью доп. элементов
// для браузеров не поддерживающих CSS-свойство border-radius.
var checkCorners = function () {
    // Ищем все элементы с классом corners
    $('.corners').each(function () {
        // Если для данного элемента браузер не поддерживает border-radius
        if (!isBorderRadiusSupported(this)) {
            // Добавляем внутрь элемента доп. элементы реализующие уголки
            wrapInCorners(this);
        }
    });
};

// DOM загрузился и готов к обходу и манипуляциям
$(function () {
    checkCorners();
});

Плюсы решения:

  • HTML льется чистый, без доп. элементов, и, соответственно, лишняя разметка не мозолит глаз ни в статической верстке, ни в XSL-шаблонах.
  • В прогрессивных браузерах (примерно у четверти пользователей) уголки рисуются средствами браузера (максимально быстро).
  • Есть задел на будущее — если IE или Opera надумают реализовать border-radius, то наше решение готово к этому радостному событию.

UPD: В комментариях указали на слабые стороны этого решения. Также откликнулись мои коллеги и помогли улучшить решение по производительности. Спасибо всем! Для этого мне и нужен блог — поделиться решением, собрать фидбек и найти более удачный вариант.

1. Не нужно проверять поддержку border-radius для каждого элемента. Сделаем эту проверку один раз в теге <head> до загрузки контента и добавим соответствующий класс у элемента <html>.

// Проверяет, поддерживает ли браузер CSS-свойство border-radius.
var isBorderRadiusSupported = function () {
    var s = document.documentElement.style;
    return typeof s.borderRadius === "string" ||
           typeof s.WebkitBorderRadius === "string" ||
           typeof s.KhtmlBorderRadius === "string" ||
           typeof s.MozBorderRadius === "string";
};

document.documentElement.className += isBorderRadiusSupported() ?
        " border-radius" : " no-border-radius";

2. Создавать из строки ноды под каждый уголок недешево, а зааппендить их в DOM еще дороже. Вставка каждого уголка это рефлоу блока, в который он вставляется. Выгоднее просто отдавать доп. разметку сразу в HTML. Задействовать доп. разметку будем в зависимости от класса, который мы выставили у элемента <html>.

.no-border-radius .corners .lt {
    // Стили для уголков
}

3. На текущий момент в Опере нет поддержки border-radius. Не стоит гонять балластный код ради неизвестного будущего. Поэтому отрываем -opera-border-radius: 4px; и соответствующую проверку в JS.

UPD2: Opera 10.50 pre-alpha поддерживает border-radius, причем без префикса. Ура!

22.05.2009

Будущее яваскрипта: ECMAScript 5

Очень впечатлился выступлением товарищей из ECMAScript о грядущих изменениях в языке. Рекомендую посмотреть всем яваскриптерам.

Слайды презентации

12.05.2009

Gmail: Двоеточие в id-шнике

Продолжение статьи «Особенности верстки Gmail».

Мой коллега Иван Широков заметил, что все id-шники в верстке Gmail начинаются с двоеточия.

Хочу поделиться с вами своими соображениями, зачем это может быть сделано.

Во-первых, чтобы нельзя было (даже по ошибке) навесить стили для такого id-шника, потому что #:my {color: red;} не работает. Своеобразная защита, которая как бы подчеркивает, что id-шник используется только в яваскрипте.

Во-вторых, и в главных, чтобы элемент не был доступен по идентификатору в глобальной области видимости (global namespace). Когда мы пишем <div id="my">test</div>, этот дивчик становится доступен через идентификатор my в глобальной области видимости. То есть alert(my.tagName) выдаст «DIV» (сюрприз, сюрприз!). А если писать с двоеточием <div id=":my">test</div>, то уже хренушки — нет такого идентификатора в глобальной области видимости. При этом элемент по-прежнему можно достать через document.getElementById(':my'). На самом деле это очень круто, так как уменьшается вероятность коллизии в глобальной области видимости.

Все это лишь мои догадки. А вы что думаете?

08.04.2009

Дилемма о загрузке JS-файлов

С одной стороны, конечно, хочется накладывать JS сразу по мере загрузки HTML. Иными словами, хорошо бы делать инициализацию JS-кода компонента сразу после загрузки HTML-кода компонента:

<div id="something">
    ...
</div>
<script>
    Something.init();
</script>

Понятно зачем — чтобы свести к минимуму задержку между двумя событиями: 1) HTML компонента отобразился и 2) JS компонента загрузился и инициализировался.

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

Разумеется, для этого нужно грузить JS-код компонента вперед HTML-кода (обычно это делают в <head>).

***

С другой стороны, следуя рекомендациям ведущих специалистов по борьбе с тормознутостью сайтов, хочется вынести загрузку всех JS-файлов в конец страницы (перед закрывающим тегом <body>).

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

Однако, минус очевиден — увеличивается задержка между рассмотренными выше событиями 1) и 2). То есть мы получаем, что пользователь дольше видит HTML с еще не наложенным яваскриптом.

***

Что делать? Я думаю, что правильный путь — это все-таки вынести загрузку всех JS-файлов в конец страницы, при этом в <head> грузить один маленький легковесный скрипт, который при попытке пользователя взаимодействовать с компонентом при еще не наложенном JS, будет сигнализировать ему, что, мол, все окей, подожди чуть-чуть, JS грузится и скоро наступит счастье.

Вот и Мэтт Снайдер похожим путем идет. А как вы решаете эту дилемму?

05.04.2009

JSLint

Валидировали, валидировали, да не вывалидировали.

Думаю, многие пробовали программу JSLint — валидатор для проверки корректности JavaScript-кода. Однако блиц-опрос знакомых разработчиков показал, что почти все пользуются им лишь изредка, и едва ли кто задумывался о том, чтобы сделать JSLint неотъемлемой частью процесса разработки.

JSLint — сверхценный инструмент в арсенале JavaScript-разработчика. Должен сказать, что с тех пор, как я стал систематически им пользоваться, мой код стал значительно лучше. Ведь помимо отлова мелких багов и опечаток, JSLint способствует написанию стройного, слаженного, читабельного кода.

Главное — не перегибать палку

Не стоит принимать отчет JSLint как истину в последней инстанции и сразу бросаться править код. Это всего лишь рекомендация, поэтому нужно включить у себя в голове thinking mode и по каждой ошибке в отчете решить самому — согласиться с JSLint или нет. Благодаря тому, что у JSLint есть множество опций, позволяющих указать какие ошибки искать, а какие игнорировать, этот инструмент можно легко настроить на свой вкус.

Варианты использования

1. Самый простой — пойти на сайт JSLint и скопипейстить свой код в форму валидации.

2. Виджеты JSLint Widget, JSLint Multi widget, JSLint Dashboard widget for Mac OS X.

Можно драгндропнуть JS-файл или целую папку в окошко виджета, и получить отчет JSLint. В настройках виджета есть полный комплект опций JSLint.

JSLint Widget

Чем больше объем JS-кода, тем больше хочется интеграции со средой разработки (IDE). Также для крупных проектов желательно сделать валидацию рутинной операцией, выполняющейся автоматически, например, на этапе сборки и упаковки JS-кода.

3. В Aptana есть встроенный JSLint, который изначально выключен. Включить можно в настройках: Preferences → Aptana → Editors → JavaScript → Validation. После этого код будет валидироваться по мере того, как вы его печатаете.

JSLint в Аптане

К сожалению, у JSLint в Аптане нет опций.

4. Плагин к Eclipse. В начале этого года компания RockstarApps выпустила JSLint Eclipse Plugin. Опции все есть, но нет возможности указать predefined global variables, что, конечно, обломно.

5. Rhino и Windows Script Host. На одном из проектов мы используем Rhino для запуска JSLint. Написали скрипт, который обходит все проектные JS-файлы, для каждого из них вызывает JSLint и формирует общий отчет — очень удобно! Пожалуй, надо будет написать отдельный пост про это :-)

JSLint продолжает развиваться. Следить за обновлениями можно в группе JSLint, куда часто пишет сам Крокфорд, автор JSLint.

А вы валидируете свой JavaScript-код?

15.03.2009

Передача параметров из HTML в JavaScript

Часто при инициализации JS-компонента нужно найти определенный DOM-элемент с которым компонент будет работать, а также достать из HTML параметры (настройки). О способах хранения и передачи в JS этих параметров я и хотел бы поговорить.

Раньше я решал эту задачу так: простые параметры передавал через классы, сложные — через дополнительные (не всегда валидные) атрибуты или дочерние элементы.

Предположим, у нас есть DOM-элемент:

<span id="test">тест</span>

При клике на этот элемент мы хотим показывать некий попап. Для показа попапа нужно задать следующие параметры: ширина, высота, таскаемость попапа и текст. Раньше я написал бы примерно следующее:

<span id="test" class="width_250 height_150 draggable"><span class="text">Трам-парам!</span>тест</span>

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

Так бы и мучился дальше, если бы мой коллега Дима Филатов не подсказал гораздо более элегантное решение — прописать атрибут onclick в котором возвращать объект с настройками.

<span id="test" onclick="return {width: 250, height: 150, draggable: true, text: 'Трам-парам!'};">тест</span>

Как достать этот объект:

var element = document.getElementById('test');

// Забираем параметры
var options = element.onclick instanceof Function ? element.onclick() : {};

// Заметаем следы - обнуляем onclick
element.onclick = null;
element.removeAttribute('onclick');

// Навешиваем обработчик onclick для показа попапа
element.onclick = function () {
    showPopup(options);
};

По сути, для передачи настроек используется формат JSON в котором есть шесть типов значений: объект, массив, строка, число, булевское значение и null. Используя эти типы, можно передавать довольно сложную структуру данных, немыслимую для передачи через классы.

По-моему, очень круто. Блин, почему я сам не додумался до этого? :)

14.02.2009

JavaScript!

Не перестаю удивляться сложности задач, которые можно решить с помощью JavaScript.

Один чувак написал распознавалку капчей сайта Megaupload. Джон Ресиг разобрал как это сделано.

Другой чувак заебошил карту мира на канвасе.

(Ну не говоря уже про Gmail, Google Reader и Google Docs.)

29.12.2008

JS: image.onload

Делал прелоадилку картинок и столкнулся с проблемой — в Опере у картинки иногда не срабатывал обработчик события onload.

Оказалось, проблема была в том, что я сначала задавал src и потом навешивал обработчик onload, а нужно делать наоборот. Причина проста: если картинка находится в кэше, то в Опере после задания src она загружается мгновенно, и событие onload срабатывает еще до того, как мы навесили обработчик onload.

Я наблюдал этот эффект только в Опере, хотя не исключаю, что может проявиться и в других браузерах.

Правильный код:

var image = document.createElement('img');
image.onload = function () {
    // вызывается всегда
    alert('image loaded');
};
image.src = 'test.jpg';

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

11.11.2008

Книги по JavaScript

За последние пару лет появилось много хороших книг по яваскрипту, что не может не радовать. Еще совсем недавно можно было посоветовать только «наименее плохую» книгу (Флэнагана). Сегодня же я могу порекомендовать вам три отличные книги.

John Resig
Pro JavaScript Techniques
Книга недавно вышла на русском языке

Джон Ресиг (JavaScript-евангелист в Mozilla Corporation и автор библиотеки jQuery) в своей книге рассказывает об объектно-ориентированном JavaScript, написании кода для повторного использования, тестировании и отладке, DOM, Events, Ajax.

К сожалению, в книге невероятно много опечаток, в том числе в примерах кода, что особенно неприятно. Также не понравилось, что иногда вместо подробного объяснения какой-либо темы, Ресиг приводит ссылки на статьи в интернете (на того же Крокфорда) где эта тема раскрыта. Я люблю уютно устроиться долгим зимним вечером в обнимку с книгой, и хочу чтобы книга была самодостаточна, чтобы не нужно было ползти за ноутбуком.

Оценка: 4

Не смотря на эти недостатки, я собираюсь заказать следующую книгу Ресига — Secrets of the JavaScript Ninja, выход которой запланирован на конец 2008 года. Оглавление выглядит впечатляюще и многообещающе.

Douglas Crockford
JavaScript: The Good Parts

Дуглас Крокфорд (главный по JavaScript в Yahoo!) хорошо известен как один из самых крутых специалистов по JavaScript. Дуглас — автор формата обмена данными JSON и сервиса по проверке корректности кода JSLint. Надо сказать, что его мегамощные лекции и статьи меня реально торкнули, поэтому его книгу я ждал с особым нетерпением.

Книга невелика по объему (всего 145 страниц), но насыщена информацией. Крокфорд пишет просто и понятно, без многословных и замысловатых выражений. Я не всегда стопроцентно с ним согласен, тем не менее, его аргументы весьма разумны, а его мнение стоит того, чтобы к нему прислушаться.

Что мне особенно понравилось в книге Крокфорда (а также в JSLint) — это внимание к стилю написания кода, как сделать код максимально ясным, кратким, однозначным, как уменьшить шанс возникновения ошибок.

Оценка: 5, must read

Ross Harmes, Dustin Diaz
Pro JavaScript Design Patterns

Супермегаохуенная книга о применении паттернов проектирования в JavaScript. Я давно мечтал прочитать такую. Организация кода, уменьшение зависимостей компонентов, как сделать код более гибким — вот главные темы книги.

Очень понравилось как авторы четко расставляют все по полочкам — для каждого паттерна приведены за и против его использования, какой выигрыш паттерн может дать, и в чем при этом можно потерять.

Оценка: 5+, must read

01.09.2008

JavaScript: область видимости переменных

В JavaScript область видимости переменных не ограничивается блоком {}, а ограничивается только функцией. Например, переменные объявленные внутри for или if продолжают жить после того, как эти for и if отработали.

for (var i = 0; i < 10; i += 1) {
    var a = i * i;
    // do something
}
document.writeln(i);    // 10
document.writeln(a);    // 81

if (true) {
    var b = "test";
    // do something
}
document.writeln(b);    // test


Поэтому, лучше явно обозначить это в коде.

var i, a;
for (i = 0; i < 10; i += 1) {
    a = i * i;
    // do something
}

var b;
if (true) {
    b = "test";
    // do something
}


Во многих языках программирования рекомендуется объявлять переменные как можно ближе к месту первого использования. В JavaScript все используемые в функции локальные переменные лучше объявлять в самом начале функции.

var myFunction = function () {
    // Объявление всех локальных переменных, включая переменные циклов
    // Код функции
}

02.08.2008

Элементы с id порождают глобальные переменные в JS

Для всех элементов с атрибутом id в джавасрипте создается глобальная переменная. Следующий пример во всех браузерах выдаст алерт с "DIV".

<html>
<head>
    <script>
        window.onload = function () {
            alert(myDiv.tagName);
        };
    </script>
</head>
<body>
    <div id="myDiv">test</div>
</body>
</html>

Это плохо, но в принципе не страшно, если вы:

  • Избегаете создавать свои глобальные переменные.
  • Не забываете объявлять свои переменные. Если вы забыли объявить переменную (var myVar), то джавасрипт считает, что это глобальная переменная (implied global variable).

30.06.2008

JavaScript + CSS

Задача: Подружить JavaScript и CSS, чтобы при отсутствии JavaScript применялись немного другие стили.

Предположим, что у нас есть блок <div id="block">блок</div>, который изначально должен быть скрыт и появляться по нажатию на псевдоссылку <span id="link">псевдоссылка</span>. Если же джаваскрипта нет, то блок должен показаться сразу, а псевдоссылка спрятаться с глаз долой.

Вариант 1. С использованием тега <noscript> (олд скул)

<style type="text/css">
    #block {display: none;}
</style>

<noscript>
    <style type="text/css">
        #block {display: block;}
        #link {display: none;}
    </style>
</noscript>

Это работает, но приходится создавать отдельный кусок стилей. К тому же валидатор ругается что есть сил, поскольку <style> запрещен внутри <noscript>, а <noscript> запрещен внутри <head>. Плохой способ, не рекомендую так делать.

Вариант 2.

Гораздо лучше пойти по пути последовательного улучшения (progressive enhancement), суть которого заключается в том, что сначала мы добиваемся корректной работы в самом простом, доступном окружении (в данном случае — в браузере с неработающим джаваскриптом), а затем постепенно улучшаем наше решение, добавляя функционал для повышения удобства пользователей в браузерах поддерживающих этот функционал.

Это означает, что стили изначально должны быть настроены для случая отсутствия джаваскрипта. Далее загружается джаваскрипт, находит наш блок и скрывает его, находит псевдоссылку и показывает ее. Недостаток такого решения в том, что блок может «моргать» — пользователь может увидеть его на какое-то короткое время (пока загружается и выполняется джаваскрипт).

Вариант 3.

Как можно побороть моргание блока? Я использую такое решение. В теге <head> добавляем простую строчку:

<script type="text/javascript">document.documentElement.className = "js";</script>

Если джаваскрипт включен, элементу <html> присвоится класс js. Что важно, это произойдет до загрузки контента. Теперь можно написать такие стили:

#link {display: none;}

.js #link {display: inline;}
.js #block {display: none;}

16.06.2008

Keyboard navigation

Некоторым людям (особенно с двигательными расстройствами) сложно пользоваться мышью для просмотра веб-страниц. Вместо мыши они используют предоставляемую браузерами клавиатурную навигацию при помощи клавиши Tab. Добавляя на страницу JavaScript, нужно подумать об этих людях и предусмотреть навигацию с помощью клавиатуры.

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

Код на jQuery:

$("#test").click(function () {
    doSomething();
});
$("#test").keydown(function (event) {
    // Allow the Enter key to activate that element
    return event.keyCode != 13 || doSomething();
});

Далее, хорошо бы прописать стили для псевдокласса :focus. Хотя бы такие же, как и для :hover.

#test:hover,
#test:focus {color: #cc0000;}

Чтобы также ховерилось и фокусировалось в IE, нужно навесить обработчики для событий onmouseover/onfocus и onmouseout/onblur в которых, соответственно, нужно добавлять и убирать какой-нибудь класс.

Все круто, но с помощью таба можно пройтись только по ссылкам и элементам форм. Другие элементы по умолчанию не получают фокус. Что делать, если активный элемент, на который мы навесили onclick и onkeydown, не ссылка, а <span>?

Для этого существует атрибут tabindex.

tabindex="0" — разрешает элементу получать фокус в порядке в котором он идет в документе.

tabindex="5" — положительное значение позволяет изменить порядок получения фокуса элементом при навигации табом.

tabindex="-1" — отрицательное значение исключает элемент из навигации табом.

Проверил, работает в IE и Firefox. Опера и Сафари, к сожалению, вообще пока не поддерживают навигацию табом.

Пример клавиатурной навигации: ездилка на сайте вакансий Евросети.

P. S. Кстати, а знаете ли вы, что Shift + Tab перемещает фокус в обратном порядке?

07.06.2008

Крокфорд жжет

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

Другое дело — книга Дугласа Крокфорда «JavaScript: The Good Parts». Душа радуется!

Пример вложенного объекта:

var flight = {
    airline: "Oceanic",
    number: 815,
    departure: {
        IATA: "SYD",
        time: "2004-09-22 14:55",
        city: "Sydney"
    },
    arrival: {
        IATA: "LAX",
        time: "2004-09-23 10:42",
        city: "Los Angeles"
    }
};

Пример добавления метода у массива:

Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};

Array.method('reduce', function (f, value) {
    var i;
    for (i = 0; i < this.length; i += 1) {
        value = f(this[i], value);
    }
    return value;
});

...

// Create an array of numbers.

var data = [4, 8, 15, 16, 23, 42];

// Define two simple functions. One will add two
// numbers. The other will multiply two numbers.

var add = function (a, b) {
    return a + b;
};

var mult = function (a, b) {
    return a * b;
};

// Invoke the data's reduce method, passing in the
// add function.

var sum = data.reduce(add, 0); // sum is 108

// Invoke the reduce method again, this time passing
// in the multiply function.

var product = data.reduce(mult, 1); // product is 7418880

Только недавно досмотрел второй сезон. Я не тормоз, просто смакую :)

23.05.2008

JavaScript: контекст для вложенных функций

that и this сидели на трубе…

Как известно, this внутри вложенных функций (inner functions) равен глобальному объекту (global object). Из-за этого приходится писать нелепую конструкцию var that = this; (господи, сколько же раз я написал эту строчку!)

Пример:

var test = {
   init: function () {
      this.txt = 'Test!';
      var that = this;
      window.setTimeout(function() {
         alert(that.txt);
      }, 1000);
   }
};

Как же избавиться от ненавистного that?

Для этого попробуем воспользоваться методом apply, который позволяет задать контекст (чему будет равен this) у функции. Однако, применить apply к callback-функции передаваемой в setTimeout нельзя, поскольку apply сразу выполняет функцию к которой он применен. Поэтому напишем вспомогательный метод scope.

Function.prototype.scope = function (o) {
   var fn = this;
   return function () {
      return fn.apply(o, arguments);
   };
};

Теперь перепишем наш пример, используя метод scope.

var test = {
   init: function () {
      this.txt = 'Test!';
      window.setTimeout(function () {
         alert(this.txt);
      }.scope(this), 1000);
   }
};

Ура, мы избавились от that!

Плюсы:

  • Выкинули var that = this;
  • В inner function используем «родную» переменную this вместо трижды злоебучей that
  • Мне кажется, код стал выглядеть элегантнее

Спорные моменты:

  • Экономии объема кода нет, может быть даже небольшое увеличение
  • Возможно, ухудшилась читаемость кода, поскольку, не дочитав до конца определения функции, непонятно, на что же ссылается this

Изначально хотел опубликовать эту заметку в Техногрете, но передумал — не могу с высокой трибуны рекомендовать решение, которым, возможно, сам не буду пользоваться :)

26.04.2008

Вращение автомобиля на сайте ZAZ Sens

Не вдаваясь в подробности реализации, хочу поделиться парой идей, лежащих в основе вращения автомобиля на сайте ZAZ Sens.

1. Для каждого цвета все восемь ракурсов автомобиля собраны в одном файле JPG. Вот, например, файл для цвета «Зеленый мох»:

Почему я решил объединить все ракурсы в один общий файл? Во-первых, размер общего файла получается меньше. Во-вторых, меньше обращений на сервер. И, наконец, JavaScript-код для прелоада и управления одним файлом проще, чем для прелоада и управления восемью.

Эта картинка стоит бэкграундом у дивчика размером с один ракурс автомобиля. При вращении меняется background-position у дивчика и показывается соответствующий ракурс:

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

Поэтому изначально все цвета задизейблены, а в JavaScript я ставлю картинки с собранными ракурсами на загрузку (для всех цветов). По событию onload соответствующий цвет энейблится (код упрощен для наглядности):

var oImage = document.createElement('img');
oImage.onload = function(){ /* включаем зеленый цвет */ });
oImage.src = '/f/1/car-sea-green.jpg';

Таким образом цвета включаются по мере загрузки цветовых файлов.

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

Если бы я сделал последовательную загрузку, то есть чтобы следующий цветовой файл начинал загружаться только по завершению загрузки предыдущего, для пользователя это было бы лучше — он быстро бы получил возможность вращать один цвет, а пока он его вращает, догрузились бы остальные. К сожалению, руки так и не дошли переделать.