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

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

21.12.2009

input type="file" multiple

Оказывается, Safari 4, Chrome 3 и Firefox 3.6 Beta 4 поддерживают выбор сразу нескольких файлов с помощью атрибута multiple.

<input name="att" type="file" multiple="true"/>

Сафари рядом с кнопкой показывает количество выбранных файлов, плюс при поднесении мышки вылезает тултип с названиями файлов:

Еще более удивительно то, что Opera уже давно поддерживает multiple-файлы с помощью атрибутов min и max.

<input name="att" type="file" min="1" max="99"/>

При этом в Опере меняется название кнопки.

Обычный инпут:

С атрибутами min и max:

Очень хочется верить, что multiple заимплементируют в IE 9. Тогда можно будет, наконец, полностью отказаться от дурацкой флешки.

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, причем без префикса. Ура!

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 грузится и скоро наступит счастье.

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

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. Используя эти типы, можно передавать довольно сложную структуру данных, немыслимую для передачи через классы.

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

26.03.2008

HTML: контейнеры

Как бы вы заверстали копирайт? Варианты ответов:

<p id="copyright">(с) 2005 Company Inc.</p>

<div id="copyright">(с) 2005 Company Inc.</div>

<div id="copyright">
   <p>(с) 2008 Company Inc.</p>
</div>

Первый вариант ограничивает нас только одним абзацем. Если со временем потребуется добавить второй абзац копирайта, то верстку придется переделать. Кроме того, представим, что вдруг потребовалось вставить в копирайт блок с какой-то сложной версткой (очередное дизайнерское извращение). Тег <p> не может содержать блоковых элементов, поэтому опять-таки придется переделать.

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

Однако, я выбираю третий вариант, не смотря на то, что он самый длинный. Тег <p> добавляет еще немного семантики — получается «параграф текста внутри блока копирайта».

Еще один плюс: в версии без стилей блоки текста не слипнутся.

Простой пример.

В версии без стилей текст слипнется:

<div id="some_additional_info">Some additional info.</div>

<div id="copyright">(с) 2008 Company Inc.</div>

А так не слипнется:

<div id="some_additional_info">
   <p>Some additional info.</p>
</div>

<div id="copyright">
   <p>(с) 2008 Company Inc.</p>
</div>

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