12 авг. 2010 г.

Честный undefined

В коде jQuery наткнулся на красивый способ получения «честного» undefined:

(function(undefined) {

// тут мы имеем настоящий undefined

})();

Напомню, что глобальный undefined можно легко переопределить:

window.undefined = 'some_value';

Вышеприведенный же способ позволяет иметь в своем коде настоящий, «защищенный» undefined, неподверженный переопределению извне.

Кроме этого, добавляются «бесплатные» бонусы:

  • undefined становится локальным внутри кода, обращение к нему будет быстрее, чем к глобальному undefined
  • внутри такого кода undefined будет пожат обфускаторами кода, в отличие от глобального

По-моему, очень изящно.

12 нояб. 2009 г.

Tagged EventBus

Развивая идею loose coupling, заложенную в паттерне EventBus, хочу предложить полезное, на мой взгляд, улучшение — тэгирование событий.

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

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

В таком случае, обычные попапы при открытии посылают событие примерно так:

$.eventBus.trigger('popup open');

Попапы с авторизацией так:

$.eventBus.trigger('popup auth open');

Те слушатели, которые хотят слушать открытие любых попапов, включая попапы авторизации, подписываются так:

$.eventBus.bind('popup open', handlerForPopups);

А те, которые слушают открытие только попапов авторизации, так:

$.eventBus.bind('popup auth open', handlerForAuthPopup);

Т.е. если множество тэгов при bind является подмножеством множества тэгов при trigger — то обработчик события срабатывает.

Простейшую реализацию Tagged EventBus можно скачать с code.google. UPD: реализация допилена

22 июн. 2009 г.

Delete vs null

Заметил, что у меня очень долго выполняется очистка памяти от ненужных объектов.

Оказалось, что присваивание null, выполняя примерно ту же функцию очистки памяти что и delete, работает быстрее, в зависимости от браузера:

  • IE6 — примерно в десять раз,
  • IE8 — примерно в четыре раза,
  • Firefox 3 — примерно вдвое,
  • Opera — примерно на 10-20%.

Разница между delete и присваиванием null в том, что, во-первых, после delete значение становится undefined, во-вторых, удаляется не только значение, но и ключ, таким образом, исчезая из циклов вида for in, в отличие от присваивания null.

Если вышеперечисленное не играет роли, то можно смело пользоваться присваиванием null вместо delete.

13 июн. 2009 г.

Думай о ссылках

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

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

Еще из похожей темы — когда у ссылки есть иконка, не являющейся ссылкой. Как так? Это же одно целое.

Другая распространенная «болезнь» ссылок — ссылки на странице на саму себя. В навигации, в тексте, да все равно где. Не должно быть такого. Любая ссылка должна совершать какое-то действие, переход. Казалось бы, это общеизвестная истина, тем не менее, нарушаемая на подавляющем большинстве сайтов. Если ваш «супердвижок» или «супершаблонизатор» не позволяют сделать этого — не прикрывайтесь этим, переписывайте движок.

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

28 мая 2009 г.

Микропаттерны оптимизации в JavaScript: декораторы функций debouncing и throttling

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

Debouncing

Если дословно переводить — «устранение дребезга». Такой декоратор позволяет превратить несколько вызовов функции в течение определенного времени в один, причем задержка начинает заново отсчитываться с каждой новой попыткой вызова. Возможно два варианта:

  1. Реальный вызов происходит только в случае, если с момента последней попытки прошло время, большее или равное задержке.
  2. Реальный вызов происходит сразу, а все остальные попытки вызова игнорируются, пока не пройдет время, большее или равное задержке, отсчитанной от времени последней попытки.

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

Например, у вас есть suggest. Посылать запросы к серверу на каждый keyup расточительно и не нужно. Можно декорировать обработчик, чтобы он срабатывал только после того, как пользователь перестал нажимать на клавиши, допустим, в течение 300 миллисекунд:

function onKeyUp() { ... };

$('input[name=suggest]').keyup($.debounce(onKeyUp, 300));

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

$('input').bind('keyup blur', $.debounce(process, 300));

Throttling

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

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

Например, на resize окна (или, допустим, на mousemove) у вас срабатывает какой-то тяжелый обработчик. Можно его «затормозить»:

$(window).resize($.throttle(doComplexСomputation, 300));

В итоге, функция будет выполняться не чаще, чем раз в 300 миллисекунд.

Мои реализации в виде jQuery-плагина можно скачать с code.google.

Ps. Навеяно книжкой Николаса Закаса «Professional JavaScript for Web Developers, 2nd Edition», хотя в ней он путает debounce и throttle, называя первое вторым. На ajaxian тоже поднималась эта тема.

20 мая 2009 г.

Загрузка по требованию и jQuery

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

У jQuery есть механизм, позволяющий осуществить это — $.getScript, однако, он обладает рядом недостатков:

  • не запоминаются уже загруженные или загружаемые в данный момент скрипты, при повторном запросе опять идет их загрузка.
  • нельзя указать сразу несколько скриптов
  • выключен кэш (к каждому урлу насильно приписываются параметры типа ?_=1242843920520). Зачем это было так жестко сделано, для меня осталось загадкой.
  • у коллбэка нельзя задать контекст (это вообще болезнь коллбэков jQuery, хотя, как недавно рассказал Азат, разработчиками уже делаются шаги в этом направлении).

Я написал небольшой плагин, лишенный вышеперечисленных недостатков:

$.requireScript(url, callback, [context], [options])

Cкачать и попробовать можно с code.google.

13 апр. 2009 г.

Плагин для jQuery, реализующий наследование

Недавно понадобилось для проекта с jQuery реализовать наследование, написал плагин для jQuery, где релизованы идеи, описанные в «Еще раз о наследовании в JavaScript». Плагин находится на code.google — code.google.com/p/jquery-inheritance/

Работает так:

$.inherit([base], methods, [statical])

Пример:

// A — базовый тип
var A = $.inherit(
    {

        // __constructor — специальный метод, вызываемый при создании экземпляра
        __constructor : function(property) {
                this.property = property;
        },

        getProperty : function() {
            return this.property + ' of A';
        },

        getType : function() {
            return 'A';
        }

    },
    {
        // статическое свойство, доступное изнутри как this.__self.staticMember
        staticMember : 'staticA'
    });

// B — тип, наследуемый от A
var B = $.inherit(
    A,
    {

        // перекрытие с вызовом одноименного метода базового класса
        getProperty : function() {
            return this.__base() + ' of B';
        },

        // просто перекрытие
        getType : function() {
            return 'B';
        }

    },
    {
        staticMember : 'staticB'
    });

var instance = new B('value');

console.log(instance.getProperty());
console.log(instance.getType());
console.log(instance.__self.staticMember);

А кто вообще как использует, и использует ли вообще, наследование?

5 апр. 2009 г.

Знатокам JavaScript

Как это работает?

if('mySuperProperty' in window) {
    alert(window['mySuperProperty']);
}

var mySuperProperty = 1;

16 мар. 2009 г.

ZForms 3.0

Несколько недель потраченного свободного времени, переписывание кучи кода, документации, примеров — куча сил ушла на ZForms 3.0. Главное — что теперь все работает почти «само», количество кода, необходимого разработчику для описания форм, уменьшилось на порядок, а функционала только прибавилось.

27 февр. 2009 г.

Чего мне не хватает в CSS

Несмотря на всю мощь CSS, некоторых вещей мне там все-таки не хватает.

Константы

Очень часто хочется сделать какие-то правило общими для нескольких селекторов. Приходится, либо копировать часть правил из селектора в селектор:

.class1 {
  font-size: 0.9em;
  color: #FF0000;
  position: absolute;
}

.class2 {
  font-size: 0.9em;
  color: #FF0000;
  float: left;
}

Или отдельно описывать общие части:

.class1 {
  position: absolute;  
}

.class2 {
  float: left;
}

.class1,
.class2 {
  font-size: 0.9em;
  color: #FF0000;
}

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

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

.class1 {
  position: absolute;
}

.class2 {
  float: left;
}

.class3 {
  font-size: 0.9em;
  color: #FF0000; 
}

Душа же хочет чего-нибудь такого:

$font1 {
  font-size: 0.9em;
  color: #FF0000; 
}

.class1 {
  $font;
  position: absolute;
}

.class2 {
  $font;
  float: left;
}

Где-то читал, что что-то подобное поддерживает webkit.

Селекторы, с возможностями похожими на XPath

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

.container[descendant::img] {
  margin-bottom: 1em;
}

Мне кажется это гораздо более нужным чем всякие drop shadow и css-анимации.

24 февр. 2009 г.

Самые эффективные решения - самые простые

Оказывается, как просто можно в js проверить, является ли что-то массивом:

function isArray(o) {
  return Object.prototype.toString.call(o) === '[object Array]'; 
}

Источник: http://thinkweb2.com/projects/prototype/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/.

Кстати, в jQuery (начиная с 1.3), это проверяется именно этим способом.

Мне как-то всегда хватало o instanceof Array, но оказалось что у этого способа есть проблемы с фреймами. И еще, после замены instanceof Array на isArray у меня перестала течь память в IE6 в ZForms (вернее, в том случае, если ZForms использует родной Common.js).

30 янв. 2009 г.

CSS: clip и Internet Explorer

На днях понадобилось воспользоваться css-свойством clip для создания закруглений у блоков с помощью css-спрайтов. В нормальных браузерах заработало быстро. А вот разработчики IE, как обычно, выделились.

Оказалось, что в режиме quirks, IE понимает запись значений через запятую (так как написано в спецификации w3c) — clip: rect(100px, 100px, 145px, 30px), а в режиме соответствия стандартам только через пробел — clip: rect(100px 100px 145px 30px). Проверил на IE6 и IE7. Хорошо хоть остальные браузеры тоже понимают такую неправильную запись.

Чем думают разработчики IE?

9 нояб. 2008 г.

Пакетирование js- и сss-ресурсов. Управление подключением ресурсов с помощью XML/XSL.

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

Я расскажу о способе, который использую в своем фрэймворке «Конфигуратор», однако, если вашим проектам не чужды понятия XML и XSL, вы легко можете адаптировать предлагаемый способ к своим нуждам.

Если вкратце, то я предлагаю разбивать все js- и css-ресурсы проекта на пакеты, исходя из их функциональности. Внутри пакета собирать входящие в него файлы и минимизировать их. В режиме разработки отдавать исходники как они есть, а в «боевом» — собранные по пакетам и минимизированные (для уменьшения количества http-запросов и размера файлов). Кроме этого, за каждую функциональность проекта должен отвечать отдельный xsl (который непосредственно и знает о необходимых этой функциональности ресурсах). Причем все подключения, сборки пакетов, и их минимизации должны происходить при как можно меньшем количестве телодвижений со стороны разработчика.

Рассмотрим на примере двух пакетов — global (содержащего базовую функциональность проекта) и zforms (содержащий функциональность для форм). Пакет global должен подключаться везде, а пакет zforms — только на страницах форм.

Xml-описание пакетов (должно быть доступно везде внутри проекта):

<package name="global" version="1">
  <css media="screen">
    <file name="common-screen.css" dev-mode="development" />
    <file name="main-screen.css" dev-mode="development" />
    <file name="main-colors-screen.css" dev-mode="development" />
    <file name="main-screen-package.css" dev-mode="production" />
  </css>
  <css media="screen" cc="if IE">
    <file name="common-screen-ie.css" dev-mode="development" />
    <file name="main-screen-ie.css" dev-mode="development" />
    <file name="main-screen-ie-package.css" dev-mode="production" />
  </css>
  <js>
    <file name="Common.js" dev-mode="development" />
    <file name="Common_Ajax.js" dev-mode="development" />
    <file name="Common-package.js" dev-mode="production" />
  </js>
</package>

<package name="zforms" version="2.43">
  <css media="screen">
    <file name="ZForms-screen.css" dev-mode="development" />
    <file name="ZForms-screen-package.css" dev-mode="production" />
  </css>
  <css media="screen" cc="if IE">
    <file name="ZForms-screen-ie.css" dev-mode="development" />
    <file name="ZForms-screen-ie-package.css" dev-mode="production" />
  </css>
  <js>
    <file name="ZForms.js" />
  </js>
</package>

Рассмотрим подробнее из чего состоит пакет. Во-первых, у пакета есть имя (@name), и версия (@version). Во-вторых, пакет делится на блоки по типу ресурсов: css и js. Каждый такой блок может содержать несколько файлов с @dev-mode = ’development’ (которые будут подключаться в режиме разработки) и один файл с @dev-mode = ’production’ (подключаемый в «боевом» режиме). Если же используемые файлы уже собраны (например вы используете какую-то библиотеку), то @dev-mode указывать не нужно. Для примера, именно так подлючается ZForms.js.

Кроме того, блоки пакета могут содержать необязательные атрибуты:

  • для css-блоков:
    • @media (screen/print/handheld)
    • @cc (для указания conditional comments, если стили блока нужно подключать только для IE)
    • @version (версия блока)
  • для js-блоков
    • @cc (аналогично css-блоку)
    • @version (версия блока)
    • @pack, true/false (использовать ли алгоритм base-62 для сжатия production-версии)

Суммарная версия файла будет складываться из версии пакета и версии блока. Версия будет добавлена к имени файла в качестве параметра — ?v={номер_версии}. Добавление этого параметра позволяет избежать кэширования браузерами предыдущей версии файла.

Я не буду останавливаться на подробном описании xsl-шаблонов для подключения ресурсов, приведу лишь общие моменты.

В общем шаблоне для всех страниц:

<xsl:template match="&document;" mode="cdocument:head">
  <head>
    ...
    <xsl:apply-templates select="." mode="cdocument:include-packages">
    ...
  </head>
</xsl:template>

<xsl:template match="&document;" mode="cdocument:include-packages">
  <xsl:call-template name="cinclude:package">
    <xsl:with-param name="name" select="'global'" />
  </xsl:call-template>
</xsl:template>

В шаблоне для форм перекрываем:

<xsl:template match="&document;" mode="cdocument:include-packages">
  <xsl:apply-imports /> <!-- подключение остальных пакетов -->
  <xsl:call-template name="cinclude:package">
    <xsl:with-param name="name" select="'zforms'" />
  </xsl:call-template>
</xsl:template>

В итоге, получаем, что за каждый функционал отвечает отдельный xsl, который вызывает шаблон для подключения своих ресурсов и, что самое важное, вызывает через apply-imports шаблоны для подключения всех остальных ресурсов текущей страницы, причем не только из пакета global, но и из других возможных независимых пакетов (при этом ничего не зная о них).

Все файлы с @dev-mode = ’production’ можно и нужно создавать автоматически при выкладке в «боевой» режим, распарсив xml-описание пакетов.

Текущее состояние dev-mode определяется глобальной переменной $cglobal:dev-mode, которая, в свою очередь, берет его из конфига проекта.

1 нояб. 2008 г.

Победа в WebHiTech

На закончившемся первом технологическом конкурсе веб-сайтов в Рунете, ZForms.ru и «Еврогараж» заняли первое и второе места в номинации «Лучшие потребительские качества»!

15 окт. 2008 г.

Нестандартное использование xsl:copy-of. Разделение содержания и представления

Обычно конструкцию вида xsl:copy-of используют для копирования фрагмента исходного дерева в результирующее дерево. При этом забывая, что xsl:copy-of можно использовать для копирования фрагмента результирующего дерева. Вроде бы, польза от этого неочевидна, однако, я покажу, как это можно применить.

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

<div class="rounded"> <div class="corner l-t"></div> <div class="corner r-t"></div> <div class="corner r-b"></div> <div class="corner l-b"></div> <div class="content"> <!-- контент --> </div> </div>

И мы хотим обрамлять такой конструкцией свои блоки, допустим — статьи, формы, меню. Решение в лоб — в шаблоне вывода каждого такого блока добавлять такую обрамляющую конструкцию. Т.е. «копипаст» со всеми вытекающими из него последствиями.

Примерно, это выглядело бы так (возьмем блок статей):

<xsl:template match="articles"> <div class="rounded"> <div class="corner l-t"></div> <div class="corner r-t"></div> <div class="corner r-b"></div> <div class="corner l-b"></div> <div class="content"> <xsl:apply-templates select="article"/> </div> </div> </xsl:template>

Мой вариант — создать для обрамляющей html-конструкции отдельный именованный шаблон, а контент передавать ему параметром, который внутри шаблона копировать с помошью xsl:copy-of:

<xsl:template name="block"> <xsl:param name="content"/> <div class="rounded"> <div class="corner l-t"></div> <div class="corner r-t"></div> <div class="corner r-b"></div> <div class="corner l-b"></div> <div class="content"> <xsl:copy-of select="$content"/> </div> </div> </xsl:template>

Шаблоны соответствующих блоков (статей, форм, меню) будут выглядеть следующим образом:

<xsl:template match="articles"> <xsl:call-template name="block"> <xsl:with-param name="content"> <xsl:apply-templates select="article"/> </xsl:with-param> </xsl:call-template> </xsl:template> <xsl:template match="navigation"> <xsl:call-template name="block"> <xsl:with-param name="content"> <ul class="navigation"> <xsl:apply-templates select="navigation-item"/> </ul> </xsl:with-param> </xsl:call-template> </xsl:template> <xsl:template match="form"> <xsl:call-template name="block"> <xsl:with-param name="content"> <xsl:apply-templates select="." mode="zforms"/> </xsl:with-param> </xsl:call-template> </xsl:template>

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

P.S. Впервые похожую идею я когда-то услышал от Владимира Токмакова из Студии.

5 сент. 2008 г.

Еще раз о наследовании в JavaScript

Всегда смущала обычная реализации наследования в JavaScript, а в особенности вызов метод базового класса из метода потомка. Здесь и в дальнейшем я буду использовать термин «класс» из классического ООП, хотя в JavaScript заложена немного другая концепция.

Допустим, что у нас есть иерархия классов вида A → B → C и в методе наследников нам нужно вызывать тот же метод базового класса вверх по иерархии. Обычно это выглядит примерно так (вариантов может быть несколько, но смысл не меняется):

// описание методов класса A ... method : function(param1, param2) { // действия класса A } ... // описание методов класса B ... method : function(param1, param2) { // действия базового класса A A.prototype.method.call(this, param1, param2); // действия класса B } ... // описание методов класса С ... method : function(param1, param2) { // действия базового класса B B.prototype.method.call(this, param1, param2); // действия класса C } ...

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

// описание методов класса B ... method : function(param1, param2) { // действия базового класса A this.__base(param1, param2); // действия класса B } ... // описание методов класса С ... method : function(param1, param2) { // действия базового класса B this.__base(param1, param2); // действия класса C } ...

Возможно ли такое в JavaScript? Оказывается, возможно.

Читая блог Dean Edwards’а, я наткнулся на интересную идею реализации подобного функционала — при наследовании не просто записывать в прототип нового класса перекрываемый метод, а заворачивать его в метод-обертку, которая будет перед вызовом метода заменять this.__base на необходимый метод базового класса, а после вызова восстанавливать (потому что возможно ситуация когда перекрываемый метод вызывает другие методы, которые также могут вызывать одноименные методы базовых классов). Тоже самое можно сделать и для конструктора.

В итоге, наследование у меня происходит сейчас так:

var SubClass = BaseClass.inheritTo( { // если нужно, перекрываем конструктор __constructor : function(param) { // если нужно, вызываем конструктор базового класса this.__base(param); // и делаем что-нибудь еще }, // перекрываемый метод method : function(param) { // если нужно, вызываем метод method базового класса this.__base(param); // и делаем что-нибудь еще } }, { // если необходимо, тут перекрываем статические свойства и методы } );

Для единообразия базовый класс создается так (наследуется от абстрактного класса Abstract):

var Class = Abstract.inheritTo( { // конструктор __constructor : function(param) { }, // обычный метод method : function(param) { } }, { // если необходимо, тут создаем статические свойства и методы } );

Работоспособность этого способа была проверена на иерархии классов ZForms (около 30 классов). После рефакторинга код стал намного стройнее, короче и понятнее.

27 авг. 2008 г.

CSS-селекторы для Common.js

Начал писать свою реализацию (плагин для Common.js) css-селекторов, реализованных в большинстве фреймфорков. Пока результаты весьма радуют — на большинстве из тех селекторов, которые уже успел реализовать (на данный момент это практически все css2-селекторы), скорость гораздо выше, чем у всех остальных фреймворков. Для тестов использую SlickSpeed Selectors Test.

13 авг. 2008 г.

ZForms.ru

Наконец-то хватило сил доделать ZForms.ru.

14 июл. 2008 г.

Выравнивание чекбоксов и радиобаттонов с их лэйблами

Всегда, даже работая в Студии, как-то обходил эту проблему стороной.

Но тут перфекфионизм взял верх и решил, наконец, сделать «круто». Вкратце, в чем состоит проблема — у одних браузеров (ie, opera) есть некоторый отступ у чекбоксов и радиобаттонов (который нельзя ничем сбросить), у других нет.

Кусок исходного html-кода:

... <dl> <dt> <label>Пол</label> </dt> <dd> <div class="option"> <input type="radio" name="sex" id="sex-male" value="male" /> <label for="sex-male">мужской</label> </div> <div class="option"> <input type="radio" name="sex" id="sex-female" value="female" /> <label for="sex-female">мужской</label> </div> </dd> </dl> ...

Решение для «нормальных» браузеров:

.option { position: relative; } .option input { position: absolute; left: 0; top: 0.69em; // Величина сдвига подобрана для line-height: 1.2 margin: -4px 0 0; } .option label { margin-left: 18px; }

Теперь нужно «разобраться» с ie и opera, имеющих пресловутые отступы. Начнем c ie:

.option input { // margin: -7px 0 0 -3px; }

Кто использует conditional comments — выносит вышестоящую конструкцию в файл стилей для ie.

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

html:first-child .option input { margin: -7px 0 0 -3px; }

И вуаля, везде красота! Даже при масштабировании шрифта.

P.S. Никогда не пишите в css конструкцию вида: * { padding: 0 }. Если вам, конечно не все равно, как выглядят ваши формы в Firefox. Firefox сбрасывает внутренний отступ у input, а у select не сбрасывает. Потом будет проблематично их выровнять по высоте.

10 июл. 2008 г.

jQuery-адаптер для ZForms

Написал адаптер для ZForms, позволяющий работать через jQuery.

Напомню, что изначально ZForms работал только через Сommon, но было бы неправильно заставлять использовать разработчиков несколько фреймворков на проекте, если им нужны возможности ZForms.

Вкратце, идея адаптера довольна проста — подменить вызовы родной библиотеки (Common) на вызовы jQuery, при этом не изменяя код ZForms.

Например, код для добавления css-класса dom-элементу в адаптере выглядит следующим образом:

var Common = { Class : { ... add : function( oElement, sClassName ) { $(oElement).addClass(sClassName); }, ... }, ... };