Основы HTTL

HTTL – это язык шаблонов, подобный Apache Velocity, который компилируется в быстрый, исполняемый байт код. В ηCMS используется модифицированная версия httl движка https://github.com/Softmotions/httl

Пример HTTL кода:

#if(books)
    #for(Book book: books)
        <td>${book.title}</td>
    #end
#end

По умолчанию в HTTL включены 6 директив: #set, #if, #else, #for, #break, #macro.

Наличие базовых знаний языка java существенно облегчит понимание синтаксиса и конструкций HTTL.

Вывод результата выражений HTTL

Формат:

${expression}

Пример:

${user.name}

В данном случае результат выражений преобразуется в html код страницы так, чтобы он не был валидной html разметкой. Например, <b>text</b>, как результат выражения, преобразуется в &lt;b&gt;text&lt;/b&gt; . Для того, чтобы отключить это преобразование, используется конструкция $!:

$!{expression}

В этом случае разработчик шаблона должен быть уверен, что результат выражения $!{expression} не вызовет проблем с безопасностью при отображении страницы.

Если результат выражения равен null, то HTTL выведет пустую строку:

#set(String a = null)
"${a}" == ""

Выведет: “” == “”

Комментарии в HTTL

Строчные комментарии помечаюся, как ## в начале строки:

## Это комментарий

Блочные коментарии начинаются с #* и заканчиваются *#:

#*
    Это
        блочный комментарий в HTTL
*#

Escape директивы

Escape директива #[...]#

Формат:

#[этот блок не HTTL]#

Пример:

#[This is no parse block: #if ${name}]#

Escape $ и # символов

Формат:

\#
\$
\\

Символ \ перед #, $, \ выводит эти символы как есть, исключая их из HTTL разметки.

Выражения

Примечание

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

  • Если в цепочке вызовов ${foo.bar.blabla} один из элементов вернет null, то все выражение будет интерпретировано как null, а при выводе преобразовано в пустую строку.

  • Оператор == соответствует сравнению java объектов с помощью .equals. Иными словами, foo == bar эквивалентно foo.equals(bar) в java.

  • Выражение в одинарных или двойных кавычках интерпретируется как строка. Если есть необходимость использовать одиночный символ (типа char) то мы его заключаем в обратные кавычки ``.

  • + в выражениях, где первый аргумент - число, будет интерпретироваться как

    арифметическое сложение. Например: ${1 + “2”} выведет 3 вместо 12. Для конканценации строк используйте пару: ${s1}${s2}.

  • Доступ к значениям свойств экземпляров java классов осуществляется по имени свойств Например, ${user.name} эквивалентно вызову ${user.getName()}.

  • Результат выражения с логическим OR является последним ненулевым/непустым элементом выражения. Например, результатом выражения ${list1 || list2} будет list1, если list1 не пуст, в противном случае результатом будет list2.

  • Числовые long литералы могут быть заданы как <number>L или <number>l. Например, 3L или 3l. В случае, если используется L, результатом будет объект класса java.lang.Long, а для маленького l результатом будет примитивный long.

  • Для доступа к данным в списках java.util.List или в ассоциированных коллекциях java.util.Map можно использовать оператор квадратные скобки []. Например, выражение ${mylist[0]} эквивалентно ${mylist.get(0)}, а ${mymap[‘foo’]} эквивалентно ${mymap.get(“foo”)}.

  • Результатом выражения ${[“a”, “b”, “c”]} является java.util.List содержащий эти элементы:

    #for(color: ["red","yellow","blue"])
        ${color}
    #end
    
  • Результатом выражения: ${[“foo”:”bar”, “foo2”:”bar2”]} является java.util.Map с отношениями foo => bar и foo2 => bar2:

    #for(entry: ["red":"# FF0000","yellow":"# 00FF00"])
        ${entry.key} = ${entry.value}
    #end
    
  • Прямое обращение к статическим методам при помощи префикса @:

    ${@java.lang.Math.min(1,2)}
    ${@Math.min(1,2)}
    

Дополнительно отметим поддержку instanceof и new операторов:

${user instanceof httl.test.model.User}
${user instanceof User}
${new httl.test.model.User("a","b","c").name}
${new User("a","b","c").name}

Вы можете использовать оператор приведения типов () в выражениях:

<img src="$!{((Image) asm('imageA')).link}"></img>

Это приведение результата вызова метода asm к экземпляру класса Image и вызов у него java метода .getLink()

Установка переменных #set

Формат:

#set(type name)
#set(name = expression)
#set(type name = expression)

Где name - это имя переменной, а type - java тип переменной

Пример:

#set(firstName = "John")
#set(String lastName = "Doe")

В этом примере переменная с именем firstName должны быть определена выше по шаблону:

#set(String firstName)

Условные выражения #if и #else

Формат:

#if(expression)
...
#end

Пример:

#if(user.role == "admin")
    ...
#else(user.role =="member")
    ... в противном случае если роль равна 'member'
#else
    ... если ни то и ни другое, тогда выполняется этот блок
#end

Каждый #if должен завершаться #end после набора опциональных #else директив.

Обработка условного выражения

  • Для не-Boolean результата эквивалентом истины(true) является:
    • число, отличное от нуля
    • непустая строка
    • непустая коллекция
    • объект, который не null
  • #if(expression) эквивалентно #if(expression != null && expression != false && expression != “”)

  • #if(object) эквивалентно #if(object != null)

  • #if(string) эквивалентно #if(string != null && string != “”)

  • #if(collection) эквивалентно #if(collection != null && collection.size > 0)

Итерация по коллекциям #for

Формат:

#for(name: expression)

#for(type name: expression)

Пример:

#for(books: books)
    ${for.index}
    ${for.size}
    ${for.first}
    ${for.last}
#end

В теле блока for определен объект for со следующими свойствами:

  • for.index - текущий номер итерации, начиная с 0
  • for.size - размер коллекции, по которой происходит итерация
  • for.fist - первый элемент коллекции
  • for.last - последний элемент коллекции

Явное определение типа элемента коллекции:

#for(Book book: booklist)
    ${book.title}
#end

В данном примере явно определяем тип элемента коллекции, к которому приводится каждый элемент.

Выполнить девять раз:

#for(9)

Вывести от одного до девяти:

#for(i: 1..9)

Вывести 10, 20, 30, где аргумент определен, как массив []:

#for(i: [10, 20, 30])

Взять для итерации первое непустое множество books1 или books2:

#for(book: books1 || books2)

Итерации по сумме двух множеств:

#for(book: books1 + books2)

Сортировать коллекцию, затем произвести по ней итерацию:

#for(book: books.sort)

Рекурсивная итерация, элементы меню имеют метод getChildren, которые возвращают коллекцию подэлементов. Итерация по всем элементам в данной иерархии:

#for(Menu menu: menus.recursive("getChildren"))

Прерывание цикла с помощью #break

Формат:

#break
#break (expression)

В случае, если expression возвращает true или непустую строку, выполнение цикла будет прервано.

Примечание

Делайте условный #break прямо в теле директивы:

#break (i ​​== j) ## правильно

Это существенно лаконичней и более производительно, чем:

#if (i == j) #break #end

Выполнение действия, если коллекция пуста #for #else

Формат:

#else
#else(expression)

Пример:

#for(book: books)
        ...
#else
        ... # выполняется когда коллекция пуста
#end

Библиотеки функций в контексте HTTL шаблонов

Регистрации библиотеки методов и доступные методы

В контексте HTTL шаблонов доступны библиотеки переиспользуемых методов. Библиотека переиспользуемых методов это java класс с публичными статическими методами. Библиотека может быть зарегистрирована с помощью параметра конфигурации HTTL import.methods.

Пример регистрации новой библиотеки методов в HTTL:

import.methods+=com.mycompany.MyHttlMethods

После регистрации библиотеки все публичные статические методы класса библиотеки становятся доступными в контексте HTTL шаблона и их можно переиспользовать.

По умолчанию в HTTL определены следующие библиотеки:

import.methods=httl.spi.methods.LangMethod,\
               java.lang.Math,\
               httl.spi.methods.SystemMethod,\
               httl.spi.methods.StringMethod,\
               httl.spi.methods.MathMethod,\
               httl.spi.methods.TypeMethod,\
               httl.spi.methods.CollectionMethod,\
               httl.spi.methods.CodecMethod,\
               httl.spi.methods.EscapeMethod,\
               httl.spi.methods.FileMethod,\
               httl.spi.methods.MessageMethod

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

Описание некоторых из методов стандартной библиотеки HTTL.

Вызов библиотечных методов HTTL

Формат вызова метода:

${name(arg1, arg2, ...)}
${name()}
${arg1.name}
${arg1.name()}
${arg1.name(arg2, ...)}

Где name - название метода, а arg1, arg2, ... - возможные аргументы метода.

Предположим, мы зарегистрировали библиотеку MyHttlMethods, как было описано выше. В нашей библиотеке - один простой метод, который добавляет Hello к переданной в качестве аргумента строке:

package com.mycompany;

public class MyHttlMethods {

    public static String hello(String name) {
        return "Hello " + name + "!";
    }
}

Из контекста HTTL этот метод может быть вызван следующими эквивалентными способами:

  1. ${hello(“Andy”)}

  2. ${“Andy”.hello}

  3. ${‘Andy’.hello}

  4. #set(String name = "Andy")
    ${hello(name)}
    ${name.hello}
    

Каждый из которых выведет:

Hello Andy!

Как можно видеть, первый аргумент метода может быть как аргументом явного вызова метода ${hello(name)}, так и контекстом для вызова этого метода без первого аргумента: ${name.hello}.

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

package com.mycompany;

public class MyHttlMethods {

    public static String hello(String name) {
        return "Hello " + name + "!";
    }

    public static String hello(String name, String msg) {
        return hello(name) + " " + msg;
    }
}

Тогда, в дополнение к существующим возможностям, мы сможем вывести Hello Andy! Great to see u! любым из ниже перечисленных способов:

${hello("Andy", "Great to see u!")}

${"Andy".hello("Great to see u!")}

Пример использования метода :js:func:`toCycle` из `httl.spi.methods.CollectionMethod`

Вывод списка продуктов с циклически меняющимся цветами строк из набора colors:

#set(colors = ["red","blue","green"].toCycle)
<table>
#for(item: list)
    <tr style="color:${colors.next}">
        <td>${item.name}</td>
    </tr>
#end
</table>

Макросы #macro

Макрос - это блок HTTL разметки, который можно переиспользовать. Макрос может принимать набор параметров, аналогично параметрам функции в java. При вызове макроса HTTL разметка, определенная в макросе, вставляется в место вызова макроса.

Формат определения макроса:

#macro(name)
#macro(name(arg1, arg2, ...))
#macro(name(type1 arg1, type1 arg2, ...))

Где name - имя макроса, arg1, arg2, ... - возможные аргументы макроса, type1, type2, ... - опциональные типы аргументов макроса.

Формат вызова макроса:

${name(arg1, arg2)}

Где name - имя макроса, arg1, arg2, ... - возможные аргументы макроса.

Макросы могут применяться в случае наследования HTTL шаблонов.

Включение в разметку других файлов

Семейство include методов из httl.spi.methods.FileMethod позволяют включать другие файлы разметки в текущую разметку.

Пример: включение контента template.httl в разметку:

${include("/template.httl")}

Передача дополнительных аргументов при включении:

${include("/template.httl", ["arg":"value"])}

Использование относительного пути до файла:

${include("../template.httl")}

Примечание

Файл, включаемый при помощи метода include, интерпретируется как HTTL разметка.

Включение содержимого файла в текущее место разметки:

${read("/text.txt")}

Примечание

Файл, включаемый при помощи метода read, не интерпретируется как HTTL разметка.