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 классов). После рефакторинга код стал намного стройнее, короче и понятнее.