Синтаксис шаблонов

Синтаксис шаблонов Wasaby является XHTML-совместимым. Используя шаблоны, вы можете в декларативной форме связывать данные контрола с элементами DOM. Для шаблонов мы используем собственное расширение файла — WML (Wasaby Markup Language). Необходимым условием валидности шаблона является наличие единственного корневого элемента.

<!-- WML -->
<div>
    <!-- контент шаблона -->
</div>

Конструкция шаблонизатора

Шаблонизатор Wasaby распознаёт конструкции, созданные с помощью синтаксиса Mustache (двойных фигурных скобок). Выражение этих конструкций можно составлять из переменных, находящихся в области видимости шаблона, а также из синтаксических конструкций языка JavaScript.

<!-- WML -->
<div>
    {{ выражение }}
</div>

При обработке шаблона вычисляется выражение конструкции, его значение экранируется и, как следствие, выводится на странице как простой текст (не HTML). Таким образом обеспечивается защита от XSS-уязвимостей.

Существуют и другие конструкции, которые будут разобраны далее в рамках руководства.

Вывод текста

По умолчанию значение конструкций выводится на страницу как текст. В следующем примере показано как вывести в шаблон значение переменной text.

<!-- WML -->
<div title="{{ text }}">
    {{ text }}
</div>

Вывод JsonML

Значение конструкции можно вывести как HTML. Для этого необходимо использовать Controls/decorator:Markup. Контрол принимает данные формата JsonML, а перед вставкой выполняет санитизацию небезопасных тегов.

<!-- WML -->
<div>
    <Controls.decorator:Markup value="{{ json }}" />
</div>
// JavaScript
var ModuleClass = Base.Control.extend({
    _template: template,
    json: '[["p", {"version":"2.0"}, ["a", {href: "https://saby.ru"}, "https://saby.ru"]]]'
});

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

<!-- WML -->
<div>&#8381;
</div>

Результат:

<div>
    ₽
    ₽
</div>

Использование JavaScript

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

<!-- WML -->
<div>
    <div>{{ true }}</div>
    <div>{{ 25 }}</div>
    <div>{{ 24 + 8 }}</div>
    <div>{{ 'hello' }}</div>
    <div>{{ "Hello" + ", " + "Wasaby!" }}</div>
    <div>{{ ({'a': 5, 'b': 'blue'}).a }}</div>
    <div>{{ ['days','color'][1] }}</div>
    <div>{{ number + 1 }}</div>
    <div>{{ bar && number }}</div>
    <div>{{ bar ? "hidden" : "visible" }}</div>
    <div>my-class {{ bar ? number }}</div>
    <div>{{ number > 20 }}</div>
</div>

См. демо-пример на CodeSandbox

В примере выше использован тернарный оператор:

<!-- WML -->
<div>my-class {{ bar ? number }}</div>
В JavaScript такой синтаксис оператора считается некорректным, однако в шаблонах Wasaby Framework допустим к использованию.

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

<!-- WML -->
<div>
    {{ var name = 'Wasaby'; "Hello" + name }}
    {{ console.log('debug') }}
    {{ if (bar) number }}
</div>

См. демо-пример на CodeSandbox

Условия

Директива ws:if

Директива <ws:if> позволяет добавлять html-разметку при выполнении условия, описанного в атрибуте data. Значением атрибута является конструкция шаблонизатора. Когда атрибут принимает значение true, в DOM вставляется содержимое блока <ws:if>.

<!-- WML -->
<div>
    <ws:if data="{{ someOpt === 'saby' }}">
        <a href="https://saby.ru/">saby.ru</a>
    </ws:if>
</div>

См. демо-пример на CodeSandbox

Директива имеет короткую форму записи — атрибут if, который можно назначить любому html-тегу в шаблоне. Смысл атрибута идентичен атрибуту data. Когда атрибут принимает значение true, в DOM вставляется и содержимое тега, и сам тег.

<!-- WML -->
<div>
    <a href="https://saby.ru/" if="{{ someOpt === 'url' }}">saby.ru</a>
</div>

Директива ws:else

Директиву <ws:if> можно дополнить блоками <ws:else>. Для всех блоков, кроме последнего, допускается создание условия в атрибуте data.

<!-- WML -->
<ws:if data="{{ someOpt === 'saby.ru' }}">
    <a href="https://saby.ru/">saby.ru</a>
</ws:if>
<ws:else data="{{ someOpt === 'wi' }}">
    <a href="https://wi.sbis.ru/">wi.sbis.ru</a>
</ws:else>
<ws:else>
    <a href="https://online.sbis.ru.ru/">online.sbis.ru</a>
</ws:else>

Между блоками <ws:if> и <ws:else> запрещается встраивание разметки. Это приводит к синтаксической ошибке.

Область видимости директивы

Директивы <ws:if> и <ws:else> не создают новую область видимости переменных (см. Подробнее о шаблонах), поэтому переменные родительского шаблона доступны к использованию в любом из блоков директивы.

Циклы

Директива ws:for

С помощью директивы <ws:for> можно реализовать добавление фрагментов разметки в цикле. Для описания выражения цикла используют атрибут data, а тело цикла описывают в содержимом блока <ws:for>.

В атрибуте data допускается организовывать итерации по целочисленному диапазону, объектам, массивам и экземплярам классов Types/entity:Model или Types/entity:Record. Для каждого типа данных используется собственная форма записи.

Итерация по целочисленному диапазону или элементам массива:

<!-- WML -->
<div>
    <ws:for data="i in number">
        {{i}};
    </ws:for>
</div>

См. демо-пример на CodeSandbox

Итерация по свойствам объекта или элементам массива:

<!-- WML -->
<div>
    <ul>
        <ws:for data="key, value in food">
            <li>{{ key }} is {{ value }};</li>
        </ws:for>
    </ul>
</div>

См. демо-пример на CodeSandbox

Директива <ws:for> имеет короткую форму записи — атрибут for, который можно назначить любому html-тегу в шаблоне. Смысл атрибута идентичен атрибуту data.

<div>
    <ul>
        <li for="goods in food">{{ goods.name }} is {{ goods.color }};</li>
    </ul>
</div>

См. демо-пример на CodeSandbox

Область видимости директивы

Директива создаёт новую область видимости переменных (см. Подробнее о шаблонах). Переменные видны только внутри оператора, но не за его пределами.

Встраивание контрола или внешнего шаблона

По имени

Для добавления в шаблон контрола можно применять специальный синтаксис, где имя контрола — это имя тега. Если в имени встречается символ "/", тогда при добавлении в шаблон его заменяют на символ точки.

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

<!-- WML -->
<div>
    <!-- Встраивание контрола -->
    <Spoiler.Module />
     
    <!-- Встраивание контрола из JS библиотеки-->
    <Demo.foo:Module />
</div>

См. демо-пример на CodeSandbox

Директива ws:partial

Для добавления в шаблон контрола или другого шаблона можно применять директиву <ws:partial>. Директиве в атрибут template передают имя контрола или шаблона (с указанием плагина wml!).

<!-- WML -->
<div>
    <!-- Встраивание шаблона -->
    <ws:partial template="wml!Demo/OtherTemplate"/>
     
    <!-- Встраивание контрол -->
    <ws:partial template="Spoiler/Module"/>
</div>

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

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

<!-- WML -->
<div>
    <!-- Значение атрибута вычисляется динамически -->
    <ws:partial template="{{ name }}"/>
</div>

Передача опций

Добавленный контрол/шаблон создаёт изолированную область видимости, в которой недоступны переменные родительского шаблона. Однако эти переменные можно передать в качестве опций, и делают это с помощью атрибутов.

В рамках контрола или шаблона работа с опциями подчиняется этим правилам.

В следующем примере показано, что в области видимости родительского шаблона существует переменная caption и name. С помощью атрибутов newCaption и newName мы создали опции и присвоили им значения.

<!-- WML -->
<div>
    <Spoiler.Module newCaption="{{ caption }}" /> 
    <ws:partial template="wml!Demo/OtherTemplate" newName="{{ name }}" />
</div>

См. демо-пример на CodeSandbox

Шаблонизатор поддерживает синтаксис XML для описания передаваемых опций. Для этого следует использовать следующую форму:

<ws:optionName>
    <ws:optionType>optionValue</ws:optionType>
</ws:optionName>
<!-- WML -->
<div>
    <Spoiler.Module> 
        <ws:myOpt1>
            <ws:Number>20</ws:Number>
        </ws:myOpt1>
        <ws:myOpt2>
            <ws:String>New message</ws:String>
        </ws:myOpt2>
        <ws:myOpt3>
            <ws:Boolean>true</ws:Boolean>
        </ws:myOpt3>
        <ws:myOpt4>
            <ws:Object>
                <ws:key>
                    <ws:Number>1</ws:Number>
                </ws:key>
                <ws:title>
                    <ws:String>Пункт меню</ws:String>
                </ws:title>
                <ws:folder>
                    <ws:Boolean>true</ws:Boolean>
                </ws:folder>
                <ws:style>
                    <ws:Object>
                        <ws:title>
                        <ws:String>Пункт меню</ws:String>
                        </ws:title>
                        <ws:key>
                        <ws:Number>1</ws:Number>
                        </ws:key>  
                    </ws:Object>
                </ws:style>
                <ws:countries>
                    <ws:Array>
                        <ws:String>Russia</ws:String>
                        <ws:String>Ukraine</ws:String>
                        <ws:String>Belarus</ws:String>
                    </ws:Array>
                </ws:countries>
            </ws:Object> 
        </ws:myOpt4>
        <ws:myOpt5>
            <ws:Array>
                <ws:Number>1</ws:Number>
                <ws:Object key="1" title="Russia" />
                <ws:String>Пункт меню</ws:String>
                <ws:Boolean>true</ws:Boolean>
            </ws:Array>
        </ws:myOpt5>
        <ws:myOpt6>
            <ws:Function arg1="{{value1}}" arg2="{{value2}}">MyModule/MyControl:prototype.myFunction</ws:Function>
        </ws:myOpt6>
    </Spoiler.Module>
</div>

Полный список вариантов конфигурации опций см. в демо-примере на CodeSandbox.

Передача атрибутов

Для встроенного контрола/шаблона используется другой синтаксис для обозначения атрибутов — префикс attr. Описанные таким образом атрибуты будут добавлены на корневой DOM-элемент.

В следующем примере показано, что на корневой DOM-элемент будет добавлен атрибут class со значением myClass.

<!-- WML -->
<div>
    <Spoiler.Module attr:class="myClass"/>
</div>
/* CSS */
.myClass {
   color: red;
}

См. демо-пример на CodeSandbox

Встроенный шаблон

С помощью директивы <ws:template> вы можете объявить встроенный шаблон — фрагмент вёрстки, предназначенный для локального использования в рамках родительского шаблона. Встроенный шаблон объявляют до момента его использования, обычно в начале файла. В атрибуте name задают имя.

В области видимости встроенного шаблона доступны все переменные из области видимости родительского шаблона.

Для добавления встроенного шаблона используют директиву <ws:partial>.

<!-- WML -->
<div>
    <ws:template name="myTemplate">
        <p>Список разделов</p>
            <ul>
            <ws:for data="key, value in links">
                <li><a href="{{value}}">{{key}}</a></li>
            </ws:for> 
            </ul>
    </ws:template>
    ...
    <ws:partial template="myTemplate" />
</div>

См. демо-пример на CodeSandbox

Обработчик события внутри встроенного шаблона и добавление шаблона через цикл ws:for

В настоящий момент при использовании директивы ws:for есть ограничение. Не поддерживает вывод встроенного шаблона, где используется обработчик события.