понедельник, 17 ноября 2014 г.

Linux ACL

Для каждого сайта уместно создать следующую структуру каталогов:

/srv/www/sitename
    conf
    cron
    log
    www
        js
        css

/srv/www/sitename/conf/sitename.conf:

<VirtualHost *:80>
    ServerAdmin admin@sitename
    DocumentRoot "/srv/www/sitename/www"
    ServerName sitename
    ServerAlias www.sitename

    ErrorLog "/srv/www/sitename/log/error.log"
    CustomLog "/srv/www/sitename/log/access.log" common
    LogLevel warn

    <Directory "/srv/www/sitename/www">
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
        DirectoryIndex index.php
    </Directory>
</VirtualHost>

Добавляем права пользователя:

useradd sitename -d sitename
cd /srv/www/sitename
chown -R sitename:sitename .
setfacl -Rm d:u:www-data:rwX,u:www-data:rwX .

Запускаем сайт:

ln /srv/www/sitename/conf/stiename.conf /etc/apache2/sites-available/sitename.conf
a2ensite sitename

среда, 29 октября 2014 г.

CSS: псевдоклассы и псевдоэлементы

Псевдоклассы - это фантомные классы с заранее определенными именами, которые присваиваются элементам при соблюдении определенных условий (наведении мыши на элемент, вводе корректных данных и т.п.)

Ссылки, мышь и клавиатура:
:link - применяется к ссылкам, которых нет в истории браузера.
:visited - применяется к ссылкам, которые попали в историю браузера.
:focus - применяется к объектам, находящимся в фокусе (выделены клавишей Tab)
:hover - применяется к объектам, на которые наведена мышь.
:active - применяется к активным объектам, к которым применен щелчок мыши.
Атрибуты следует определять в указанном порядке (LVFHA), т.к. каждый предыдущий "перекрывает" последующий, будучи примененным в обратном порядке.
link и visited применяется только к ссылкам (<a>), focus по умолчанию к ссылкам, кнопкам, полям ввода и текстовым областям, но при использовании атрибутов contenteditable и tabindex к любым элементам. hover и active - к любым элементам.

:target - применяется, когда атрибут id элемента совпадает с идентификатором в URL.

Формы:
:enabled, :disabled - стиль применяется к доступным (недоступным) для редактирования элементам форм. Вариант [disabled] (атрибут вместо псевдокласса) поддерживается начиная с IE7, псевдоклассы только с IE9.
:checked - применяется к элементам формы (radio, checkbox, option), , находящимся в состоянии checked. IE9+
:indeterminate - применяется к элементам формы (IE9+) или элементу progress (IE10+), находящимся в неопределенном состоянии.
:required, :optional - применяется к обязательным (необязательным) элементам формы (IE10+)
:invalid, :valid - применяется к элементам формы (input), содержащим недопустимые (допустимые) значения. Параметры валидации устанавливаются через атрибуты, большая часть из которых IE10+
:default - применяется к элементу по умолчанию (одному в группе однотипных элементов, например, кнопке submit среди прочих кнопок).
:in-range, :out-of-range - применяется к элементу формы, значение которого находится (не находится) в установленных пределах.

Дерево элементов:
:root - применимо к корневому элементу тега. В стандарте HTML5 это тег html. Однако использование псевдоэлемента имеет больший приоритет, чем использование тега (1.0 против 0.1)
:empty - применяется к элементам, не имеющим потомков и не содержащих контента. (Например, стиль с селектором p:empty{} применится для <p></p>, но не применится для <p> </p> - элемент содержит пробел).
:first-child, :last-child, :only-child - стиль применяется только к первому, только последнему или единственному (элемент не имеет сиблингов) элементу-ребёнку некоторого родителя.
:first-of-type - стиль применяется к первому элементу ребёнку некоторого родителя, если этот ребёнок имеет заданный тип. При этом элементу могут предшествовать сиблинги других типов. (например p:first-of-type {} применит стиль к первому абзацу как ребёнку тега body, даже если элементу p предшествуют иные элементы (например, заголовки).
:last-of-type - аналогичен first-of-type, но относится к последнему элементу заданного типа.
:only-of-type - аналогичен предыдущим псевдоэлементам, но относится к единственному элементу заданного типа (p:only-of-type {} применится к содержимому элемента p только если родитель p имеет в качестве ребёнка только один элемент типа p).

Для следующих четырех псевдоклассов применяется параметр n, который может принимать следующие значения:
- выражение вида (an+b, где a и b - целые числа), представляющее собой арифметическую прогрессию некоторых номеров элементов, для которых должено быть применено правило. Элементы прогрессии могут быть и отрицательными числами, однако нумерация элементов дерева DOM ведётся только для положительных величин.
- odd (то же что 2n) / even (то же, что 2n+1) для четных и нечётных элементов, к которым нужно применить некоторое правило;
- число, начиная от 1 (то же что 0n + b), определяющее номер элемента, для которого нужно определить правило;

:nth-child(n) - применить правило к элементу, если он является ребёнком с порядковым номером n, например p:nth-child(even) {} применит стиль к каждому чётному абзацу, а b:nth-child(3n) к каждому третьему жирному выделению абзаца.
:nth-last-child(n) - аналогичен предыдущему псевдоклассу, но нумерация происходит с конца.
:nth-of-type(n) - правило применяется к n-ному элементу указанного типа, определенного предшествующим селектором.
:nth-last-of-type(n) - аналогично предыдущему, с нумерацией с конца.

Прочие:
:fullscreen - правило применяется при перехода браузера в полноэкранный режим.
:lang(x) - правило применяется в случае, если используется язык x. (Фактически, эквивалентно [lang="x"], но псевдокласс может брать язык из контекста (сервер или тег meta)).
:not(x) - исключает из области действия правила все элементы, которые покрываются селектором x. p:not(.quote) {} - правило применяется для всех абзацев кроме тех, которые имеют класс .quote

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

:before, :after - позволяют разместить некоторый контент с определённым форматированием до или после CSS-бокса. Таким образом можно оформить, например цитаты (вынести знаки цитирования слева и справа или сверху и снизу от блока с цитатой), ненумерованные списки и т.п.

:first-letter, :first-line - позволяют изменить стиль первой буквы (т.н. буквицы) или первой строки элемента. Руководство по CSS-форматированию буквиц. и строк

Для того, чтобы выделить только первую букву/строку статьи (а не каждого абзаца) рекомендуется объединять эти псевдоэлементы с :first-child.

::selection - позволяет изменить стиль выделенного текста. Для FF используется -moz-префикс. Используется только с двойным двоеточием.

вторник, 28 октября 2014 г.

CSS: приоритет выполнения правил

Довольно часто один и тот же элемент может попадать под действие сразу нескольких правил CSS. Для определения какое именно из них следует выполнять можно пользоваться следующим алгоритмом.

Каждому правилу можно назначить некоторый идентификатор, состоящий из восьми чисел: A.B.C.D.a.b.c.d Из двух идентификаторов бОльшим считается тот, который имеет бОльшее число A. Если эти числа равны у двух идентификаторов, смещаемся вправо. Из двух правил применяется то, которое имеет бОльший идентификатор. Если идентификаторы равны, применяется то правило, которое объявлено последним.

Разбираем селектор на составные части (простые селекторы). Имеется 7 типов таких составных частей:

1. Универсальный селектор не весит ничего. Поэтому правило * {} будет применяться в последнюю очередь, а из двух правил div {} и div * {} применится то, которое объявили последним.
2. Каждый селектор типа увеличивает число d на единицу.
3. Каждый селектор класса увеличивает число с на единицу. Мультикласс дробится на отдельные элементы, каждый из которых увеличивает число с на единицу.
4. Каждый селектор id увеличивает число b на единицу.
5, 6, 7 - Каждый селектор атрибута, псевдокласса или псевдоэлемента считается селектором клсса и увеличивает число c на единицу.

Инлайновый стиль (включенный непосредственно в качестве атрибута тега) увеличивает на единицу число a. При этом селектор по атрибуту style ([style]) увеличивает на единицу число с, а следовательно "перебивается" инлайновым правилом:

<style>
  [style] { color: green }
</style>
...
<p style="color: red"> <!-- предпочтение отдаётся инлайновому правилу -->
...

Если блок определений правила включает ключевое слово !important, то на единицу увеличиваются числа A, B, C, D (в зависимости от типа селектора по перечисленным выше правилам). Поэтому если бы в предыдущем примере определение стиля выглядело так:

[style] { color: green !important; },

то предпочтение отдалось бы уже ему: 0.0.1.0.0.0.0.0 > 0.0.0.0.1.0.0.0

Из этих правил можно сделать вывод, что наибольшим приоритетом обладает инлайновое стилевое правило тега с модификатором !important (1.0.0.0.0.0.0.0), а наименьшим - обычное правило тега (0.0.0.0.0.0.0.1), а если быть более точным - универсальный селектор (0.0.0.0.0.0.0.0)

понедельник, 27 октября 2014 г.

CSS: основы, селекторы

CSS (cascading style sheets) - формальный язык описания внешнего вида документа, написанного и использованием языка разметки.

Набор правил CSS представляет собой последовательность вида:

header {
  color: white;
  background-color: #333;
  font-size: 1.5em;
}

где header - селектор, текст внутри фигурных скобок - блок объявлений, состоящий из отдельных объявлений. Каждое объявление представляет собой пару вида атрибут: значение;.

CSS можно включить в страницу четырьмя способами:

1) Используя атрибут style любого тега HTML:
<h1 style="color: blue; background-color: #333;">RecipeFinder</h1>
Это определение действует только для конкретного тега, для которого оно описано. Способ не рекомендуется к использованию.

2) Используя тег style HTML (внутри тега <head></head>), внутри которого можно расположить набор правил:
<style>
  header {
    color: white;
    background-color: #333;
    font-size: 1.5em;
}
</style>
Правила действую в пределах документа, для которого они описаны. 

3) Подключая внешний файл при помощи директивы @import
<style>
  @import url(css/style.css);
</style>
Это наименее рекомендуемый способ, связанный с рядом проблем (включая проблемы с производительностью)

4) Подключая внешний файл при помощи тега <link>
<link rel="stylesheet" href="css/style.css">
Это наиболее рекомендуемый способ, отделяющий разметку от стилей.

Простые селекторы (типа Х):

1. Универсальный селектор ( * )
* {} - применяет правила ко всем элементам. Может использоваться для обнуления правил.

2. Селектор типа элемента ( tag )
ul {} - применяет правила ко всем элементам типа ul.

3. Селектор класса ( .class )
.box {} - применяется ко всем элементам с атрибутом class = "box"

4. Селектор id ( #id )
#navbar {} - применяется к элементу с атрибутом id="navbar"

Предполагается, что класс - групповая характеристика, которую может иметь ряд объектов страницы (тегов), тогда как id - уникальная характеристика, которую должен иметь только единственный объект.

5. Селектор атрибутов ( [attr] )
[title] {} - применяется ко всем элементам, имеющим атрибут title.

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

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

[attr=value]

id и классы являются атрибутами, поэтому фактически, вместо селекторов id и класса можно использовать следующие конструкции:

[id="#idname"] [class="className"]

хотя так писать не принято.

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

[attr*=value] - значение атрибута attr содержит подстроку value
[attr^=value] - значение атрибута attr начинается с подстроки value
[attr$=value] - значение атрибута attr заканчивается подстрокой value
[attr~=value] - атрибут находится в списке, разделённом пробелами
[attr|=value] - атрибут находится в списке, разделённом дефисами

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

6. Селекторы псевдоклассов ( :class )
7. Селекторы псевдоэлментов ( ::element )

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

8. Селектор группировки ( s1, s2, s3 )
ul, .box, #navbar {} - применяется ко всем элементам с типом ul или с классом .box или с id === #navbar. (Или в определении не исключающее).

9. Селектор мультикласса (.class1.class2.class3)
Каждый тег может иметь несколько классов, имена которых разделены пробелами. Селектор мультикласса применяется только к тем элементам, которые содержат имена всех классов, перечисленных в селекторе:

.class1.class2 { font-size: 2em }
...
<ul class="class1">Text1</ul>
<ul class="class1 class2">Text2</ul> <!-- селектор применится здесь-->
<ul class="class2">Text3</ul>

10. Селектор мультиатрибутов ([attr1][attr2="value"][attr3|="value2"])
Каждый тег может иметь несколько атрибутов, для которых применяются различные формы селектора атрибутов.

Комбинированные селекторы (типа XY). Допускается комбинировать следующие селекторы:

11. Типа и (мульти)класса элемента (2 и 3, 2 и9):
ul.box {} - все элементы типа ul и класса .box
li.calss1.class2 {} - применяется ко всем элементам типа li и классов class1 и class2.
При этом комбинация *.class эквивалентна обычному селектору класса .class .
12. Типа и (мульти)атрибутов (2 и 5, 2 и 10):
p[title] - все элементы типа p, имеющие атрибут типа title.
13. Типа и псевдоклассов (2 и 6): a:visited {} - применяется ко всем элементам типа a и состоянием visited.
14. Типа и псевдоэлементов (2 и 7): p::before {} - вставка заданного контента перед всеми элементами типа p.

Селекторы иерархии. Теги в дереве могут находиться в следующих отношениях:

а) предок-потомок - тег потомок находится внутри тега предка на произвольном уровне вложенности;
б) родитель-ребёнок - тег ребёнок является непосредственным потомком тега предка;
в) сиблинги (братья, сёсты) - теги, расположенные на одном уровне иерархии.

<body>
  <p>Hello, <b>cruel <i>world</i></b></p>
  <br />
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>
</body>

p, b, i, br, ul, все li являются потомками body, однако только p, br, ul являются его детьми.
p, br, ul являются сиблингами, также как и все li

15. Селектор потомков (X Y (разделитель пробел))
Правило применяется ко всем тегам с селектором Y, предком которых является селектор X. Возможно несколько уровне вложенности (X Y Z T), в качестве селекторов могут применяться в т.ч. и комбинированные селекторы, а также универсальный селектор:

ul.class1 * {} - применяется ко всем потомкам типа ul со классом class1.
body * p {} - применяется ко всем потомкам типа p, содержащихся в теге body но обязательно отделённых от body каким-либо другим тегом:

<body>
  <article>
    <p>One</p> <!-- стиль применяется -->
    <p>Two</p> <!-- и здесь -->
  </article>
  <p>Three</p> <!-- а здесь нет - p - дочерний элемент относительно body -->
</body>

16. Селектор прямых потомков (X > Y)
Выбирает только прямого потомка (ребенка) Y селектора X. Также возможно наличие нескольких уровней вложенности и использование универсального селектора.

17. Строгий селектор сиблингов (X + Y)
Выбирает сиблингов определяемых селектором Y, строго следующих за элементами, опредляемыми селектором X.
div + p {} - правило применяется к тегу p, следующему за тегом div (точнее, </div> - речь идёт о сиблингах, которые находятся на одном уровне иерархии).

18. Нестрогий селектор сиблингов (X ~ Y)
Выбирает всех сиблингов, определяемых селектором Y и следующих за элементами, определяемыми селектором X.
ul ~ li {} - правило применяется ко всем тегам li (расположенным на одном уровне иерархии), следующим за тегом ul .

четверг, 23 октября 2014 г.

Underscore.js: Утилиты (Underscore.js 1.7.0, Lo-Dash 2.4.1)

Ряд функций общего назначения, часть из которых используется, преимущественно, внутри самой библиотеки:
----------------------------------------------------------------
_.noConflict()
Функция возвращает ссылку на объект _, содержащий все свойства и методы библиотеки. Основное применение - возврат к старой версии библиотеки в случае переопределения включением новой версии (или наоборот).
----------------------------------------------------------------
_.runInContext([context=root]) // Lo-Dash only
Создаёт новый (без добавленных примесей) объект Lo-Dash в контексте context (по умолчанию root) и возвращает его.
----------------------------------------------------------------
_.identity(value)
Возвращает первый аргумент (value): _.identity(x) === x;
Для функций, работающих с коллекциями, является перечислением по умолчанию.
----------------------------------------------------------------
_.constant(value)
Возвращает функцию, возвращающую первый аргумент: 
_.constant(1, 2, 3)() === 1;
----------------------------------------------------------------
_.noop()
Возвращает undefined.
----------------------------------------------------------------
_.times(n, callback, [context])
n раз вызывает функцию callback в контексте context с аргументом равным n-1 (на данной итерации).
Возвращает массив, состоящий из возвращаемых функцией callback значений.
----------------------------------------------------------------
_.random([min=0], max)                      // underscore
_.random([min=0], [max=1], [floating=false])   // lodash
Возвращает целое случайное число (для lodash - с дробной частью, если floating = true, или min/max - дробные) в интервале [min, max]. Если функция имеет только один параметр, то min полагается равным 0. Для lodash вызов функции только c параметром true возвращает дробное в интервале [0..1].
----------------------------------------------------------------
_.mixin(source)                        // underscore 
_.mixin([object], source, [options])   // lodash
Позволяет расширить объект underscore/lodash пользовательскими функциями (для lodash также произвольный объект-функцию, задаваемую необязательным параметром object). Функция для расширения задается в объекте вида: { name: function }, где name - имя функции расширения, а function - ссылка на функцию или непосредственно ее код. В lodash можно указать также объект, содержащий опции. На данный момент используется только одна опция chain типа Boolean (по умолчанию true), указывающая является ли функция для расширения "цепной".
----------------------------------------------------------------
_.iteratee(value, [context], [argCount])    // underscore only
Внутренняя функция underscore. 
----------------------------------------------------------------
_.uniqueId([prefix])
Возвращает уникальный целочисленный идентификатор, начинающийся с опциональной строки prefix. Нумерация происходит с 1 является сквозной для разных префиксов.
----------------------------------------------------------------
_.escape(string)
_.unescape(string)
Подготавливает строку string к вставке в HTML, преобразовывая символы ', ", `, &, <, > в их HTML-эквиваленты (escape) или выполняет обратное преобразование (unescape).
Возвращает модифицированную строку.
----------------------------------------------------------------
_.result(object, property)  // property - string
Если property объекта object - строка с названием функции, возвращает результат выполнения этой функции, иначе возвращает значение свойства property.
----------------------------------------------------------------
_.now()
Возвращает текущий временной штамп (количество мс прошедших с начала эпохи), используя наиболее быстрый способ.

воскресенье, 19 октября 2014 г.

Наследование и делегирование

В традиционном ООП класс рассматривается как некий чертёж или структурная схема будущего объекта, а экземпляр является воплощением класса, набором реальных данных и кода, составляющих конкретный объект. Идею наследования в традиционном классовом ООП можно представить как передачу чертежей от предков к потомкам с возможностью изменений, которые вносят в чертежи потомки. При создании объекта каждый потомок строит свой собственные конгломерат данных и кода на основании самой актуальной версии имеющихся у него чертежей (описания класса).

Каждый объект при создании получает внутреннее свойство [[Prototype]], являющееся ссылкой на объект, свойства и методы которого используются в том случае, если они не находятся у оригинального объекта. (В ряде браузеров это свойство можно менять напрямую (__proto__), но такой подход не рекомендуется). Существует целая цепочка ссылок, по которой может производиться поиск вплоть до того объекта, для которого [[Prototype]] установлен в null (такой объект можно считать корневым). В результате напрашивается следующая аналогия. В традиционном ООП каждый класс-потомок можно сравнить с набором чертежей исправленных и дополненных по ходу наследования класса. А каждый экземпляр объекта является копией, построенной по этой версии чертежей. В прототипном ООП каждый объект является законченной сущностью, а наследование предполагает заимствование методов и свойств у уже существующих объектов.

Прототип задается объекту двумя способами:

1. Напрямую в качестве параметра вызова Object.create():

Cat = { name: 'Itchy', tail: true, 
        voice: function() { return "Meow" };
var obj1 = Object.create(Cat)

2. Через переопределение свойства __proto__ у наследующего объекта:

var obj2 = {};
obj2.__proto__ = Cat;

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

console.log(Object.getOwnPropertyNames(obj1)); // > []
console.log(Object.getOwnPropertyNames(obj2)); // > []
console.log(obj1.name);    // > Itchy
console.log(obj2.voice()); // > Meow

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

obj1.name = "Tom";
console.log(obj1.name); // > Tom
console.log(obj2.name); // > Itchy

Свойство наследника может быть удалено (если его атрибут changable не установлен в false). После этого наследник получает доступ к свойству прототипа:

delete obj1.name;
console.log(obj1.name); // > Itchy

Замена же свойства у объекта-прототипа меняет его и у всех наследников, для которых не определено собственное свойство:

Cat.name = "Jack";
console.log(obj1.name); // > Jack
console.log(obj2.name); // > Jack

Важно! [[Prototype]] и __proto__ представляют собой одно и то же свойство (первое свойство - внутренний элемент реализации, второе - интерфейс доступа, который предоставляют некоторые браузеры). Это свойство указывает на следующий элемент "заимствования" в цепочке наследования, где объект должен брать свойства и методы, если у него их нет. Оба этих свойства отличаются от свойства prototype, которое имеет каждый объект, унаследованный от Object.

Прототипы не позволяют проводить множественное наследование. За один раз создается только один объект, наследующий свойства и методы прототипа. Множественное наследование можно эмулировать программно при помощи функции, копирующей все свойства исходного объекта в целевой. Таким образом реализуются т.н. примеси (mixins). Более подробно примеси рассматриваются в отдельном посте.

function extend(target, source) {
    Object.getOwnPropertyNames(source).forEach(function(key) {
      Object.defineProperty(target,key, 
      Object.getOwnPropertyDescriptor(source, key)) })

    return target
}

Прототип получается также двумя способами:

1. Как результат вызова функции Object.getPrototypeOf(object)
2. Чтением свойства __proto__ объекта.

КОНСТРУКТОР - способ создавать объекты с инициализацией в императивном стиле (в отличие от ранее рассмотренного декларативного). В качестве конструктора можно использовать любую функцию. Создание объекта происходит при вызове функции-конструктора с оператором new.
Любая функция, будучи объектом, имеет в качестве одного из унаследованных свойств свойство prototype, представляющее собой пустой объект ({}). При вызове функции в качестве конструктора внутреннее свойство [[Prototype]] объекта, создаваемого этим конструктором, получает ссылку на свойство prototype функции конструктора. 

Последовательность действий при вызове конструктора с new:

1. Создать пустой объект {}, наследующий от Object.prototype
2. Установить в [[Prototype]] создаваемого объекта ссылку на prototype конструктора, чтобы унаследовать поведение прототипа. Установить в constructor создаваемого объекта ссылку на функцию конструктор.
3. Вызвать конструктор в контексте создаваемого объекта (this внутри конструктора будет ссылкой на создаваемый объект).
4. Если функция возвращает объект (любой), сделать его возвращаемым значением функции, иначе вернуть создаваемый объект.

Важно! Конструктор может возвращать произвольный объект, в том числе уже существующий, а не только создавать новый.


Underscore.js: микрошаблоны JavaScript

Трудности перевода.

В русском языке слово "шаблон" устоялось в качестве перевода двух английских терминов - pattern и template. 

Первое понятие относится к области программной архитектуры и означает некоторые идеи или концепции, которые можно выделить во множестве программных проектов вне зависимости от языка программирования. Наиболее корректный перевод слова pattern - повторяющийся мотив (или рисунок), подобно рисунку плитки или обоев. Принятый в IT-среде перевод - шаблоны проектирования (или даже "калька" - "паттерны" проектирования).

Второе понятие (о котором и пойдёт речь в данной заметке) связано с вёрсткой веб-страниц. Шаблоном (или веб-шаблоном) называют текстовую строку (или даже файл), содержащую как обычную последовательность html-тегов, выводимую без изменений, так и специальные символы и ссылки, которые некоторая процедура заменяет на конкретные значения. Более подходящим названием для этого программного элемента был бы термин "бланк". Подобно своему бумажному прототипу веб-шаблон состоит из неизменных, заранее отпечатанных в типографии элементов, часто утвержденных некоторым стандартом и пустых клеток, ячеек или полос, которые заполняются от руки или на печатной машинке. Такие клетки и ячейки часто содержат подсказки мелким шрифтом - Ф.И.О. (фамилия-имя-отчество) или М.П. (место печати).

Немного истории.

Первые "шаблоны", которые "бланки" появились в языке КОБОЛ, ставшем стандартом в бизнес-среде. Зарплатные ведомости или различные отчеты представляют собой те самые бланки, которые содержат стандартные элементы и конкретные данные в виде фамилий и сумм. Древние языки программирования не имели такой роскоши как конкатенация строк, поэтому вывод данных, содержащих постоянные и переменные данные был довольно громоздким. Типичная функция вывода на экран могла выводить только один параметр - строку или переменную, а иногда для вывода разных типов данных требовались разные функции. Нужно было следить за всеми пробелами и переводами строк, выравниваниями, вывод разрывался в самом неподходящем месте.

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

Name: John  Surname: Smith  Country: USA  Salary: $30000

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

prints("Name: ");     prints(name); 
prints(" Surname: "); prints(surname); 
prints(" Country: "); prints(country); 
prints(" Salary: $"); printn(salary); 
println();

можно использовать такую конструкцию:

printf("Name: %s Surname: %s Country: %s Salary: $%d\n", 
name, surname, country, salary);

Первый параметр функции printf и является таким бланком-шаблоном. Значения переменных, которые передаются в функцию как остальные параметры, заменяют специальные символы %s (строковая переменная) и %d (числовая переменная).

Для работы с шаблонами в Underscore и lodash используется функция _.template и переменная _.templateSettings.

_.template(templateString, [settings])            // underscore
_.template(templateString, [data], [settings])    // lodash

Функция template является генератором функций на основании строки шаблона (templateString). Это строка может содержать три типа тегов, содержимое которых преобразуется при выводе (все остальные части строки выводятся без изменений):

<% %>  - вычисление (evaluate), RE: /<%([\s\S]+?)%>/g
<%= %> - замена (interpolate), RE: /<%=([\s\S]+?)%>/g
<%- %> - эскапирование (escape), RE: /<%-([\s\S]+?)%>/g


Содержимое тега "вычисление" - обычный JavaScript-код. Шаблонизатор заменяет код на результат его вычисления.
Содержимое тега "замена" - ключи некоторого объекта (который передается в качестве параметра сгенерированной функции). В итоговой строке ключи объекта заменяются на значения.
Тег "эскапирование" ведёт себя аналогично тегу "замена" с HTML-эскапирование результатов.

Результат выполнения _.template() - функция, которая может получать объект с данными, заполнять шаблон этими данными и возвращать текстовую строку (это может быть строка таблицы или элемент списка HTML-документа).

var tmpl = "Hello, <%= name %>!";
var data = { name: "John" };

var ftmpl = _.template(tmpl);
console.log(ftmpl(data));      // > Hello, John!

В случае lodash объект для заполнения шаблона можно передать в качестве параметра не сгенерированной функции а сразу функции template.
console.log(_.template(tmpl, data)); // Только lodash
Или же учитывая тот факт, что template возвращает функции, можно вызвать ее немедленно в обеих билиотеках:

console.log(_.template(tmpl)(data)); // lodash и underscore

Параметры шаблонов хранятся в объекте _.templateSettings, но могут перекрываться параметрами из опционального объекта settings.

Переменная _.templateSettings является объектов с ключами interpolate, evaluate, escape, содержащими соответствующие регулярные выражения для указания форматов тегов, используемых в шаблоне. Для того, чтобы использовать теги в стиле Mustache можно использовать следующие регулярные выражения:

_.templateSettings.evaluate = /\{\{([\s\S]+?\}\}/g;
_.templateSettings.interpolate = /\{\{=([\s\S]+?\}\}/g;
_.templateSettings.escape = /\{\{-([\s\S]+?\}\}/g;

По умолчанию шаблон может "заполняться" объектом с любым именем, лишь бы этот объект имел поля с ключами, совпадающими с теми, которые используются в шаблоне. Для этого внутри генерируемой функции используется крайне медленный оператор with. Если поместить в строку шаблона тег интерполяции в виде ключ.значение и указать имя объекта из которого шаблон будет брать значения, в параметре settings:

_.template("Hello, <%= data.name %>", { variable: data })(data);

вместо


_.template("Hello, <%= name %>")(data);

заполнение шаблона будет происходить гораздо быстрее (до 10 раз). Однако при использовании шаблона теперь всегда нужно будет использовать временную переменную с именем, которое "прописано" в шаблоне.