|
Гостевая книга в Libretto Jet |
Домашняя страница гостевой книги
Сохранение сообщений в базе данных
Сокращение инструментов веб-разработки
Конвергенция статического и динамического стилей программирования
В данном посте мы покажем, как создаются веб-приложения с помощью Libretto Jet.
Libretto Jet – среда разработки веб-приложений, реализующая единую модель веб-программирования.
Единая модель веб–программирования – технология, в которой единый язык программирования используется как для кодирования на сервере и клиенте, так и для запросов к базам данных.
Продемонстрируем работу Libretto Jet на примере задачи построения гостевой книги.
Гостевая книга – веб–приложение, которое дает возможность пользователю оставлять комментарии и другие сообщения, а также обмениваться мнениями с другими посетителями. Ниже мы реализуем этот сервис, который будет выглядеть примерно так:
О том, как запустить свой Web-проект в Libretto IDE рассказано здесь.
Структура проекта – это директория с названием проекта, в которой содержатся две под-директории:
libretto – для файлов с Libretto-кодом.
public – для ресурсов и публичных файлов, например, изображений, Javascript кода, CSS-файлов, и т.д.
Назовем наш проект gbook.
Директория libretto содержит четыре файла – пока почти пустых:
model.ltt будет организована работа со структурами данных (это на сервере),
view.ltt будет определен внешний вид гостевой книги (в клиентском браузере),
url.ltt будет отвечать за взаимодействие между клиентом и сервером,
control.ltt определены технические методы работы среды Libretto Jet.
Поведение Libretto-программы не зависит от файловой структуры проекта. Можно определить несколько пакетов в одном файле, а можно один пакет – в нескольких файлах (пакет – замкнутый независимый модуль Libretto–программы). Компилятор транслирует все файлы из директории libretto и всех ее под-директорий.
В директории public проекта gbook находится библиотека JQuery, которую использует в своей работе Libretto Jet.
Начнем представление 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.
HTML и XML – выражения.
HTML-выражение в определении метода hello является обычной структурой языка Libretto, которую можно свободно перемешивать с другим кодом.
В Libretto можно использовать XML-структуры как обычные выражения языка. HTML–выражения также записываются в строгом XML–формате.
В 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
Здесь:
url.
com/teacode/jet, которая реализует среду веб-разработки Libretto Jet.
view, в котором будет определен внешний вид гостевой книги.
Определим теперь сам метод page в рамках пакета view:
// view.ltt
package view
def page =
<html>
<head><title>Гостевая книга</title></head>
<body>
<h1>Гостевая книга</h1>
</body>
</html>
Вызовем метод home из адресной строки браузера:
home из пакета url по умолчанию используется для определения домашней (корневой) страницы веб–приложения. Поэтому home в адресной строке может быть опущен (рис. 1а).
Для публикации сообщения в гостевой книге пользователь должен задать свое имя и написать текст сообщения. Организуем эту возможность, определив блок редактирования сообщений в отдельном методе 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.
В результате получаем:
Чтобы поля редактирования заработали, нам нужно научиться передавать их содержимое от клиента серверу. Сначала научимся управлять кнопкой.
Начнем с простейшего примера – привяжем к кнопке "Добавить" инструкцию «выбросить окошко с надписью 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>
onclick.
jet/ajax генерирует Javascript-код, который при нажатии кнопки "Добавить" вызывает метод publish из пакета url в асинхронном режиме (в стиле AJAX).
Определим сам метод 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 и других. Некоторыми из них мы воспользуемся ниже.
Нажмем на кнопку, чтобы проверить ее работу:
Теперь нам нужно научиться передавать на сервер текущие значения элементов <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>
Изменения:
jet/ajax (строка 12). Селектор ".param" означает, что методу publish должны быть переданы значения всех элементов из класса param.
input и textarea добавлен атрибут class="param". Кроме того, добавлены атрибуты name, используемые для доступа к конкретным значениям (name="name" (с. 9) и name="msg" (с. 11)).
Селекторы в 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.
Перегружаем домашнюю страницу, вводим имя и нажимаем на кнопку:
Наш следующий шаг – научить приложение публиковать ленту пришедших сообщений.
Во-первых, добавим во 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".
Проверяем:
Как видим, наш сервис научился формировать ленту сообщений. Заметим, что после добавления сообщения в полях остаются неподчищенные значения от предыдущего ввода. Но это легко исправить:
// 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, который задает новые – пустые – значения полям ввода. Теперь все в порядке:
Нам остался заключительный шаг – сохранение сообщений в базе данных. К настоящему моменту гостевая книга уже выглядит как настоящая, однако лента сообщений нигде не сохраняется, видна она только одному пользователю, и если страницу перезагрузить, то все исчезнет.
Прежде чем добавить в наш проект долговременное хранение соообщений, скажем несколько слов о том, как в Libretto организована работа с базами данных. Вот три важных особенности:
map – это некоторый объект базы данных, то map!key – это значение объекта map на ключе key.
m.string(key) берет значение ключа key в мэпе m и интерпретирует его как строчку. Метод m.maps(key) берет значения ключа key в мэпе m и интерпретирует их как последовательность мэпов. Метод m.addl(key, val) добавляет еще одно значение val ключу key в качестве последнего.
Вернемся к нашему проекту. Организуем работу с базой данных в пакете 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))
Прокомментируем:
com/teacode/data.
db(epxr), организующий транзакции в базе с именем "gbase". Библиотечный метод jet/trand(basename, expr) создает транзакцию в базе с именем basename и выполняет в рамках этой транзакции выражение expr. Если базы с именем basename не существует, то Libretto Jet создает эту базу, пользуясь ресурсами сервера.
add, который добавляет новое сообщение к базе данных. Вызов метода db с одним параметром позволяет обернуть в транзакцию операции работы в базе. Последовательность имеющихся сообщений хранится в ключе 'posts корневого объекта базы data/root (доступ к данным базы осуществляется через ее корневой объект). Библиотечный метод data/new создает новый объект в базе.
posts, который считывает из базы имеющиеся сообщения и формирует последовательность HTML-элементов, представляющих эти сообщения.
Обратите внимание на то, что в определении метода 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", "")
}
Здесь у нас два обновления:
model (с.6).
publish добавлен вызов метода model/add (с.14), сохраняющего новые сообщения в базе.
В файле 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 в случае асинхронного взаимодействия выполняет две совершенно разные функции:
По довольно большой уже статистике применения среды Libretto Jet, она замещает Javascript в 100% случаев применения второй функции и где-то 99% применения первой (только продвинутая обработка на стороне клиента, которая не укладывается в набор 20-30 наиболее популярных инструкций, требует явного применения Javascript).
Конечно, Javascript остается ключевым игроком на стороне клиента, но его роль в Libretto Jet инкапсулируется, прячется.
Libretto является компилируемым языком. Эффективность компилируемого кода примерно равна соответствующему коду на Java. Однако сам код на Libretto в среднем в 5 раз компактнее, чем его аналог на Java. В сочетании с единой программной моделью такая компактность предоставляет принципиально иные возможности не только по разработке систем, но и по их поддержке.
Наконец, в языке сочетаются стили статического и динамического программирования. Например, на Libretto можно быстро развернуть прототип системы или сервиса в стиле динамического программирования (как на Ruby, Python или Perl), а затем методом "последовательных уточнений" внедрить в код полновесный статический контроль типов данных (как в Java или Scala).
Перечисленные свойства языка Libretto и среды Libretto Jet позволяют охарактеризовать их как принципиально новую и очень эффективную технологию разработки веб-сервисов.
На Libretto Jet удобно писать как простые и компактные веб-проекты, так и большие, распределенные и масштабируемые веб-сервисы.