The Libretto Programming Language

Строим гостевую книгу в Libretto Jet

Оглавление

В данном посте мы покажем, как создаются веб-приложения с помощью Libretto Jet.

Libretto Jet – среда разработки веб-приложений, реализующая единую модель веб-программирования.

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

Продемонстрируем работу Libretto Jet на примере задачи построения гостевой книги.

Гостевая книга

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

Начинаем работу

О том, как запустить свой Web-проект в Libretto IDE рассказано здесь.

Структура проекта – это директория с названием проекта, в которой содержатся две под-директории:

Назовем наш проект gbook.

Директория libretto содержит четыре файла – пока почти пустых:

Поведение Libretto-программы не зависит от файловой структуры проекта. Можно определить несколько пакетов в одном файле, а можно один пакет – в нескольких файлах (пакет – замкнутый независимый модуль Libretto–программы). Компилятор транслирует все файлы из директории libretto и всех ее под-директорий.

В директории public проекта gbook находится библиотека JQuery, которую использует в своей работе Libretto Jet.

Hello, world!

Начнем представление Libretto Jet с реализации классического приветствия "Hello, world!". Определим для этого метод hello:

// url.ltt
package url
  
use com/teacode/jet
   
def hello = jet/out: <h1>Hello, world!</h1>

Метод hello определен в пакете url. Мы сразу можем загрузить страницу из hello в браузер, благодаря следующей возможности Libretto Jet:

Libretto Jet позволяет вызывать методы из пакета url напрямую из адресной строки браузера.

Вызовем метод hello:

Метод hello возвращает браузеру веб-страницу (в нашем случае – восклицание Hello, world!). Страница пересылается с помощью библиотечного метода jet/out. Этот метод определен в базовой библиотеке Libretto Jet com/teacode/jet, которая импортируется в строке 4.

В определении метода hello продемонстрированы две отличительные особенности языка Libretto.

  1. HTML и XML – выражения.

    HTML-выражение в определении метода hello является обычной структурой языка Libretto, которую можно свободно перемешивать с другим кодом.

В Libretto можно использовать XML-структуры как обычные выражения языка. HTML–выражения также записываются в строгом XML–формате.

  1. Синтаксический сахар.

В Libretto запись f:x является синтаксическим сахаром для f(x). В более общем случае запись f(x1, x2): x3, x4 эквивалентна f(x1, x2, x3, x4).

Заметим, что страницу приветствия можно легко параметризовать:

// url.ltt
package url
  
use com/teacode/jet
   
def hello = jet/out: <h1>Hello, #{ this.jet/string('name) }!</h1>

Теперь:

О том, как такой код работает, расскажем чуть позже. А пока начнем реализацию гостевой книги.

Домашняя страница гостевой книги

Сформируем домашнюю страницу гостевой книги. Эта задача имеет два этапа – генерирование страницы, и ее передача клиенту. Метод page, рисующий страницу, расположим в пакете view (в файле view.ltt), а передающий страницу метод home определим в пакете url в файле url.ltt.

Так будет выглядеть url.ltt после определения в нем метода home:

// url.ltt
package url
  
use com/teacode/jet
use view
   
def home = jet/out: view/page

Здесь:

Определим теперь сам метод page в рамках пакета view:

// view.ltt
package view
   
def page = 
    <html>
      <head><title>Гостевая книга</title></head>
      <body>
        <h1>Гостевая книга</h1>
      </body>
    </html>

Вызовем метод home из адресной строки браузера:

Рис 1. Вызов метода `home`
В Libretto Jet метод home из пакета url по умолчанию используется для определения домашней (корневой) страницы веб–приложения. Поэтому home в адресной строке может быть опущен (рис. 1а).

Рис 1a. Загрузка корневой страницы, определенной в `url/home`

Ввод сообщения

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

// view.ltt
package view
    
use com/teacode/jet
    
def editor =
  <div>
    <p>Имя:</p> 
    <div><input size="30" type="text"/></div>
    <p>Сообщение:</p>
    <textarea cols="50" rows="3"></textarea>
    <button>Добавить</button>
  </div>
    
def page = 
    <html>
      <head>
        <title>Гостевая книга</title>
        <script src="/jquery/jquery.min.js"/>
        <script>#{jet/jetjs}</script>
      </head>
      <body>
        <h1>Гостевая книга</h1>
        #{editor}
      </body>
    </html>

Обратите внимание на то, что метод editor вызывается внутри HTML-выражения (строка 24). Выражение внутри скобок #{...} вычисляется, и полученное значение подставляется в HTML-код.

Libretto Jet на клиенте.  Мы включили в заголовок домашней страницы библиотеку JQuery и скрипт библиотеки Libretto Jet:

<script src="/jquery/jquery.min.js"/>

<script>#{jet/jetjs}</script>

Они необходимы для работы Libretto Jet на клиенте. Скрипт загружается из библиотеки com/teacode/jet с помощью метода jetjs.

В результате получаем:

Рис 2. Поля для редактирования сообщения

Управление кнопкой

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

Начнем с простейшего примера – привяжем к кнопке "Добавить" инструкцию «выбросить окошко с надписью Hello, World!»:

// view.ltt
package view
    
use com/teacode/jet
    
def editor =
  <div>
    <p>Имя:</p> 
    <div><input size="30" type="text"/></div>
    <p>Сообщение:</p>
    <textarea cols="50" rows="3"></textarea>
    <button onclick=#{jet/ajax: 'publish}>Добавить</button>
  </div>
    
def page = 
    <html>
      <head>
        <title>Гостевая книга</title>
        <script src="/jquery/jquery.min.js"/>
        <script>#{jet/jetjs}</script>
      </head>
      <body>
        <h1>Гостевая книга</h1>
        #{editor}
      </body>
    </html>

Определим сам метод publish:

// url.ltt
package url
   
use com/teacode/jet
use view
   
def home = jet/out: view/page
   
def publish = jet/alert("Hello, World!")

Метод jet/alert задает клиенту инструкцию выбросить окошко "Hello, World!" в асинхронном режиме. Libretto Jet предлагает около двадцати различных видов асинхронных инструкций, предназначенных для клиента – append, prepend, replace, remove, eval и других. Некоторыми из них мы воспользуемся ниже.

Нажмем на кнопку, чтобы проверить ее работу:

Рис 3. При нажатии кнопки выскакивает окно

Передача параметров на сервер

Теперь нам нужно научиться передавать на сервер текущие значения элементов <input/> и <textarea/>. Выборку тех элементов, значения которых мы хотим передать, можно задать как аргумент метода jet/ajax. Расширим код view.ltt следующим образом:

// view.ltt
package view
   
use com/teacode/jet
   
def editor =
  <div>
    <p>Имя:</p> 
    <div><input name="name" class="param" size="30" type="text"/></div>
    <p>Сообщение:</p>
    <textarea name="msg" class="param" cols="50" rows="3"></textarea>
    <button onclick=#{jet/ajax: 'publish, (), ".param"}>Добавить</button>
  </div>
   
def page = 
    <html>
      <head>
        <title>Гостевая книга</title>
        <script src="/jquery/jquery.min.js"/>
        <script>#{jet/jetjs}</script>
      </head>
      <body>
        <h1>Гостевая книга</h1>
        #{editor}
      </body>
    </html>

Изменения:

Селекторы в Libretto Jet имеют синтаксис, эквивалентный синтаксису селекторов в JQuery.

Второй аргумент jet/ajax отвечает за передачу на сервер констант (аналог input/hidden), но таких констант в нашем примере нет.

Изменим метод publish, заставив его выкидывать в окне значение поля с id="name":

// url.ltt
package url
   
use com/teacode/jet
use view
   
def home = jet/out: view/page
   
def publish = jet/alert(this.jet/string('name))

Метод jet/string получает значение элемента по его name в строковом виде. Веб параметры передаются в контексте метода publish, поэтому используем this.

Перегружаем домашнюю страницу, вводим имя и нажимаем на кнопку:

Рис 4. При нажатии кнопки в появляющемся окне выдается имя пользователя

Публикация сообщений

Наш следующий шаг – научить приложение публиковать ленту пришедших сообщений.

Во-первых, добавим во view.ltt метод, генерирующий внешний вид сообщения. Метод post (с.15), получая в качестве аргументов имя пользователя и его сообщение, формирует соответствующий HTML-элемент:

// view.ltt
package view
   
use com/teacode/jet
   
def editor =
  <div>
    <p>Имя:</p> 
    <div><input name="name" class="param" size="30" type="text"/></div>
    <p>Сообщение:</p>
    <textarea name="msg" class="param" cols="50" rows="3"></textarea>
    <button onclick=#{jet/ajax: 'publish, (), ".param"}>Добавить</button>
  </div>

def post(name:String?, text:String?) =
  <div>
    <p style="font-size:80%; font-weight: bold; margin-top:20px;">#{name}</p>
    <p>#{text}</p>
  </div>
   
def page = 
    <html>
      <head>
        <title>Гостевая книга</title>
        <script src="/jquery/jquery.min.js"/>
        <script>#{jet/jetjs}</script>
      </head>
      <body>
        <h1>Гостевая книга</h1>
        <div id="contents"></div>
        <hr/>
        #{editor}
      </body>
    </html>

Кроме того, в HTML–страницу из метода page добавлен элемент <div id="contents"></div>, в котором будет формироваться лента сообщений (c.30).

Нам осталось переделать метод publish так, чтобы он начал управлять публикацией сообщений:

// url.ltt
package url
   
use com/teacode/jet
use view
  
def home = jet/out: view/page
   
def publish = {
  fix name = this.jet/string('name)
  fix msg = this.jet/string('msg)
   
  jet/append("#contents", view/post(name, msg))
}
Мы добавили в publish получение параметров 'name и 'msg, содержащих имя пользователя и текст сообщения. Кроме того, инструкция jet/alert заменена на другую асинхронную инструкцию – jet/append(selector, expr). Эта инструкция побуждает браузер найти все элементы, соответствующие выборке selector, и добавить к их контенту выражение expr.

В нашем случае инструкция append позволяет добавить сообщение к элементу с id="contents".

Проверяем:

Рис 5. Диалог

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

// url.ltt
package url
   
use com/teacode/jet
use view
  
def home = jet/out: view/page
   
def publish = {
  fix name = this.jet/string('name)
  fix msg = this.jet/string('msg)
   
  jet/append("#contents", view/post(name, msg)). 
    jet/val(".param", "")
}
Добавлен вызов метода jet/val, который задает новые – пустые – значения полям ввода. Теперь все в порядке:

Рис 6. Поля ввода подчищаются после нажатия кнопки

Сохранение сообщений в базе данных

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

Прежде чем добавить в наш проект долговременное хранение соообщений, скажем несколько слов о том, как в Libretto организована работа с базами данных. Вот три важных особенности:

Вернемся к нашему проекту. Организуем работу с базой данных в пакете model (файл model.ltt):

// model.ltt
package model
 
use com/teacode/jet
use com/teacode/data
use view
 
def db(#expr) = jet/trand("gbase", expr)
 
def add(name:String?, msg:String?) = 
  db: data/root.addl('posts, data/new {'name to name; 'text to msg})
 
def posts = 
  db: data/root.maps('posts) as p. view/post(p.string('name), p.string('text))

Прокомментируем:

Обратите внимание на то, что в определении метода posts может обрабатываться последовательность из множества сообщений. В этом случае оператор точка "." выполняет роль итератора, генерируя нужное количество HTML-элементов (операторы цикла в Libretto отсутствуют за ненадобностью).

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

// url.ltt
package url
   
use com/teacode/jet
use view
use model
  
def home = jet/out: view/page
   
def publish = {
  fix name = this.jet/string('name)
  fix msg = this.jet/string('msg)
  
  model/add(name, msg)
   
  jet/append("#contents", view/post(name, msg)). 
    jet/val(".param", "")
}
Здесь у нас два обновления:

В файле view.ltt при загрузке домашней страницы теперь публикуется текущая лента сообщений (с.31):

// view.ltt
package view
   
use com/teacode/jet
use model
   
def editor =
  <div>
    <p>Имя:</p> 
    <div><input name="name" class="param" size="30" type="text"/></div>
    <p>Сообщение:</p>
    <textarea name="msg" class="param" cols="50" rows="3"></textarea>
    <button onclick=#{jet/ajax: 'publish, (), ".param"}>Добавить</button>
  </div>

def post(name:String?, text:String?) =
  <div>
    <p style="font-size:80%; font-weight: bold; margin-top:20px;">#{name}</p>
    <p>#{text}</p>
  </div>
   
def page = 
    <html>
      <head>
        <title>Гостевая книга</title>
        <script src="/jquery/jquery.min.js"/>
        <script>#{jet/jetjs}</script>
      </head>
      <body>
        <h1>Гостевая книга</h1>
        <div id="contents">#{model/posts}</div>
        <hr/>
        #{editor}
      </body>
    </html>

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

Разработка гостевой книги завершена.

Единая программная модель

Подведем итоги. Как мы уже сказали, в Libretto Jet реализуется единая программная модель. Это означает стремление к тому, чтобы максимальное количество операций кодировалось на одном языке – Libretto.

Речь вовсе не идет о полной блокировке внешних технологий. Программная среда Libretto Jet открыта всем технологиям, однако организована так, чтобы писать на одном языке было более удобно и более эффективно.

Сокращение инструментов веб-разработки

В примере с гостевой книгой Libretto заменил Javascript при организации взаимодействия клиента и сервера, и заменил SQL как язык запросов к базам данных.

Обратите внимание на то, что у нас даже несколько размылись границы между клиентом и сервером, сделав ненужными специальные языки обмена данными, типа XML или JSON. И сервер и клиент укладываются в единую и совместную модель типов данных Libretto.

Инкапсуляция Javascript

Отдельно следует сказать об использовании Javascript. Javascript в случае асинхронного взаимодействия выполняет две совершенно разные функции:

По довольно большой уже статистике применения среды Libretto Jet, она замещает Javascript в 100% случаев применения второй функции и где-то 99% применения первой (только продвинутая обработка на стороне клиента, которая не укладывается в набор 20-30 наиболее популярных инструкций, требует явного применения Javascript).

Конечно, Javascript остается ключевым игроком на стороне клиента, но его роль в Libretto Jet инкапсулируется, прячется.

Эффективность Libretto

Libretto является компилируемым языком. Эффективность компилируемого кода примерно равна соответствующему коду на Java. Однако сам код на Libretto в среднем в 5 раз компактнее, чем его аналог на Java. В сочетании с единой программной моделью такая компактность предоставляет принципиально иные возможности не только по разработке систем, но и по их поддержке.

Итого

Перечисленные свойства языка Libretto и среды Libretto Jet позволяют охарактеризовать их как принципиально новую и очень эффективную технологию разработки веб-сервисов.

На Libretto Jet удобно писать как простые и компактные веб-проекты, так и большие, распределенные и масштабируемые веб-сервисы.