От абстрактных к контекстным

04 Mar 2014

Элементы интерфейса на страницах сайта редко бывают абстрактными, они связаны назначением в общих блоках и их объединениях. Внутри таких блоков формируется контекст.

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

Контекстные блоки — расширенные и обьединённые абстрактные, приправленные нужными только им стилями!

Например, нужно дать элементу Error__header стили блока Header с его модификаторами.

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

  • указанием классов блоков вместе на одном узле в разметке;
  • объединением селекторов блоков вручную или при помощи препроцессора.

Объединенение классов в разметке

<h4 class="Header Header--main Header--type-error Error__header"></h4>

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

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

Объединение в вёрстке

Чтобы контекстный блок использовал стили абстрактного, можно объединять их классы в составных селекторах.

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

Для того, чтобы упростить объединение селекторов, в препроцессорах есть функция @extend, позволяющая указывать класс расширяющей сущности в блоке объявлений расширяемой.

.Header {...}
.Header--main {...}
.Header--type-error {...}

.Error__header {
  @extend .Header;
  @extend .Header--main;
  @extend .Header--type-error;
}

что даёт в итоге нужное

.Header, .Error__header {...}
.Header--main, .Error__header {...}
.Header--type-error, .Error__header {...}

Проблема может возникнуть, если модификаторы связаны с друг другом. @extend не добавит контекстный класс к .Header--main.Header--type-error, если разработчик сам не напишет @extend .Header--main.Header--type-error.

Для решения этой проблемы нужно будет написать функцию, которая будет делать то, что нам нужно:

.Header {
  font-weight: bold;
  &--main {
    font-family: 'Segoe UI', sans-serif;
  }
  &--type-error {
    color: red;
  }
  &--main&--type-error {
    font-size: 1.2em;
  }
}

Header(mods) {
  @extend .Header;
  if (match('main', mods)) {
    @extend .Header--main;
  }
  if (match('error', mods)) {
    @extend .Header--type-error;
  }
  if (match('main', mods) && match('error', mods)) {
    @extend .Header--main.Header--type-error;
  }
}

.Error__header {
  Header('main error');
}

Таким образом, появляется возможность расширения блока стилями абстрактного при помощи функции с нужными параметрами.