Jet Doc |
Оглавление
Структура метода, вызываемого клиентом
Подключение метода jet/process
Методы получения веб-параметров
Формирование запроса на загрузку новой страницы
Инструкции загрузки новой веб-страницы
Формирование запроса на корректировку текущей страницы
interval/2, interval/3, interval/4
Инструкции по корректировке текущей страницы на клиенте
Работа с транзакционной памятью на сервере
Jet – библиотека Libretto, позволяющая разрабатывать облачные сервисы в рамках единой программной модели – когда работа и на сервере, и на клиенте, и в базах данных реализуется на одном языке – Libretto. Это принципиально облегчает проектирование и разработку систем. С использованием Jet производительность разработки и поддержки проектов повышается в несколько раз.
def process
Библиотека Jet функционирует на основе Libretto Framework. При поступлении запроса с клиента на сервер Libretto Framework всегда запускает метод process
, находящийся в пакете control
. Разработчик может написать свой вариант метода process
, а может воспользоваться имеющимися разработками.
Jet предлагает собственную версию метода process
. Она основана на идее явного указания в URL имени метода Libretto, который должен быть вызван для обработки запроса. Этот подход очень удобен при разработке, поскольку он
Методы, вызываемые из URL, должны располагаться в пакете url
, либо в его подпакетах. Например, URL
http://mysite.com/mymethodинтерпретируется как вызов метода
mymethod
в пакете url
:
package url ... def mymethod = { ... }
URL
http://mysite.com/access/loginинтерпретируется как вызов метода
login
в пакете url/access
.
Параметры, включенные в URL, могут быть получены с помощью специальных методов Jet.
URL
http://mysite.com/access/find?who=JohnОбрабатывающий метод на стороне сервера:
package url use com/teacode/jet ... def find = { fix person = this.jet/string('who) ... }Метод
jet/string
считывает параметр who
в формате строки. Веб-параметры приходят в обрабатывающий метод find
через его контекст, поэтому в примере используется this
.
В Jet поддерживается два вида запросов:
Результатом работы метода, обрабатывающего запрос, должен стать набор инструкций для клиента, объясняющий, что тому делать. В зависимости от вида запроса инструкции, передаваемые из метода клиенту, могут касаться либо загрузки новой веб-страницы (методы out
, redirect
), либо проведения изменений на текущей веб-странице клиента (комплект методов, таких как append
, subst
, remove
и других).
Каждый вид запроса формируется с помощью специальных методов Jet. Для первого вида – это call
и url
. Как правило, эти методы работают в рамках гиперссылок и html форм. Например,
<a href=#jet/call('createperson, map/m {'name to 'John}, ".params")> Создай персону John <a/>
Для второго вида запросов – это ajax
и interval
. Как правило, эти методы обрабатывают события html. Например,
<div onclick=#jet/ajax('changecolor, map/m {'color to 'red})> Покрась фон красным </div>
Все упомянутые методы в качестве аргумента содержат полное имя вызываемого метода. Они также опционально могут содержать:
interval
).
Структура вызываемого клиентом метода состоит, как правило, из трех частей:
def method = { /* блок получения веб–параметров запроса*/ /* блок обработки – внутренний блок метода */ /* блок формирования инструкций для клиента */ }
В контексте методу передается мэп параметров.
Методы получения веб-параметров (string
, int
и real
) одинаковы как для первого вида запросов, так и второго.
В результате своей работы метод должен сформировать набор инструкций, который пересылается клиенту.
Для запросов первого вида основной инструкцией является метод загрузки новой страницы out
. Может также использоваться redirect
.
Для запросов второго вида Jet предлагает большое количество инструкций для изменения состояния текущей страницы – append
, css
, replace
, subst
и других.
Эти и другие методы Jet подробнее описываются ниже.
Чтобы подключить jet/process к веб-приложению, необходимо задать в пакете control
следующее определение:
package control use com/teacode/jet def process = jet/processТеперь фреймворк будет использовать для обработки клиентских запросов метод
process
библиотеки Jet.
Данные методы позволяют обработчикам запросов получать значения параметров, передаваемых с клиентской стороны. Эти методы не зависят от типа запроса. Они используются как в синхронных обработчиках, загружающих на клиент новую страницу, так и в асинхронных обработчиках, корректирующих текущую страницу.
Методы позволяют задать нужный тип значения и кардинальности.
// string считывает значение параметра key как одиночную строку, // в случае неудачи возвращается () def Any string(key: String!): String? // stringe считывает одиночное строковое значение // в случае неудачи – выбрасывается ошибка def Any stringe(key: String!): String! // strings считывает последовательность строковых значений def Any strings(key: String!): String*
Аналогично для Int
и Real
, но здесь кроме считывания происходит попытка интерпретировать значение как число соответствующего типа.
// int считывает значение параметра key, пытаясь интерпретировать его как целое // в случае неудачи возвращается () def Any int(key: String!): Int? // inte считывает одиночное значение и пытается интерпретировать его как целое // в случае неудачи – ошибка def Any inte(key: String!): Int! // ints считывает последовательность целочисленных значений def Any ints(key: String!): Int*
// real считывает значение параметра key, интерпретируя его как вещественное // в случае неудачи возвращается () def Any real(key: String!): Real? // reale считывает одиночное значение как вещественное // в случае неудачи – ошибка def Any reale(key: String!): Real! // reals считывает последовательность вещественных значений def Any reals(key: String!): Real*
def Any getkeys: String*
Получение списка переданных методу–обработчику параметров.
def Any copyparams: map/M?
Копирование ключей и значений параметров в мэп.
def call(path: String!)
def call(path: String!, pars:map/M?)
def call(path: String!, pars:map/M?, cls:String*)
Метод call
обеспечивает передачу динамических параметров веб-страницы в момент перехода по гиперссылке.
Методы call
позволяют передавать значения не только стандартных элементов (таких, как input
и textarea
), но и любых динамически изменяющихся параметров страницы. Для передачи произвольных динамических параметров используются расширенные варианты элемента input/hidden
. Например, следующий элемент позволяет передать значение скролла на данной странице:
<input type="hidden" class="param" id="scroll" data-jet="eval" name="scroll" value="$('body').scrollTop()"/>
Дополнительный атрибут data-jet="eval"
подсказывает, что перед передачей параметра на сервер, его значение должно быть получено как результат вычисления javascript–выражения в атрибуте value
. В примере выше на сервер передается текущее значение вертикального сдвига (скролла) страницы в броузере.
Например, значение данного элемента будет передаваться при прохождении по следующей динамической гиперссылке:
<a href=#call('fun,(),".param")> Вызов `fun` с передачей значения скролла. </a>Исполнение данного перехода состоит в динамической генерации html формы, содержащей нужные параметры, и передаче ее на сервер.
def url(method:String!)
def url(method:String!, constants:map/S!)
Генерирование URL, обеспечивающего вызов метода fun
с параметрами из мэпа constants
. Пример использования:
<a href=#jet/url('callmethod, map/s {'key to 2})>Вызов callmethod</a>В отличие от методов
jet/call
, методы jet/url
не позволяют передавать динамические параметры страницы на стороне клиента, позволяя оставаться в рамках "чистого" HTML.
def out(h:xml/Elem!)
def out(title: String!, content:Any!)
def out(title: String!, head:xml/Elem*, content:Any!)
def redirect(url:String!)
def ajax(fun:String!)
def ajax(fun:String!, #consts)
def ajax(fun:String!, #consts, selectors:String*)
Как правило, методы ajax
используются как обработчкики html-событий. Например,
<div onmouseover=#ajax('doit) onmouseout=#jet/ajax('undoit)> Hello <div>
Методы, реализующие "поллинг" (polling) – механизм контроля за состоянием клиента и сервера через регулярно повторяющиеся запросы.
def interval(millis: Int!, fun: String!)
def interval(millis: Int!, fun: String!, #consts)
def interval(millis: Int!, fun: String!, #consts, selectors: String*)
Например, interval/4
организует регулярную (каждые millis
миллисекунд) подачу команды от клиента серверу вызвать метод fun
с передачей ему константных значений и параметров html–страницы (аналогично методу ajax/3
).
Как правило, методы interval
вставляются в веб-страницу с помощью элемента script
:
<script> #{jet/interval(5000, 'checkit)} </script>Libretto метод
checkit
из пакета url
будет вызываться каждые пять секунд.
Данные инструкции используются в случае запроса второго типа – на изменение текущей страницы клиента. Эти инструкции знакомы программистам, поскольку они похожи на аналогичные инструкции JQuery.
Принципиальным отличием является то, что в Jet эти инструкции генерируются на серверной стороне. Это минимизирует объем содержательной информации на клиентской стороне, что, в частности, важно с точки зрения безопасности.
В случаях, когда более целесообразно выполнять данные операции на клиенте, разработчик может использовать стандартные средства Javascript и JQuery. Jet не отменяет, а дополняет возможности стандартных средств.
Общая структура инструкций за редким исключением одна и та же:
instruction(selector:String!, value)Здесь
selector
– селектор JQuery (например, ".class"
), задающий выборку html элементов, к которым должна применяться инструкция. value
– данные, которые используются при применении инструкции.
Если необходимо выполнение нескольких инструкций, то их комплект формируется в методе в виде пути (через точку '.'
):
package url use com/teacode/jet ... def addPerson = { fix person = this.jet/s('who) jet/subst("#person", person). jet/append(".person_list", <li>#{person}</li>) }В данном примере метод генерирует две инструкции:
id="person"
и
"person_list"
.
def addClass(selector: String!, class: String!)
Добавляет класс с именем class
к html элементам веб-страницы, соответствующим селектору.
def after(selector:String!, val:Any*)
Вставляет контент val
после элемента (элементов), соответствующих селектору.
def alert(value:Any*)
Выбрасывает окошко alert
с содержимым value
.
def append(selector:String!, val:Any*)
Вставляет контент val
в качестве последнего под-элемента элементов, соответствующих селектору.
def attr(selector:String!, attrname:String!, attrvalue:String?)
Добавляет в элемент или убирает из элемента (если attrvalue==()
) атрибут с заданным именем и значением.
def before(selector:String!, val:Any*)
Вставляет контент перед элементами, соответствующими селектору.
def css(selector:String!, propname:String!, propvalue:String?)
Добавляет элементу или убирает из элемента (если propvalue==()
) свойство CSS.
def eval(cd:String!)
Позволяет выполнить произвольное Javascript-выражение, например, jet/eval(<<alert("Hello, world!")>>)
.
def hide(selector:String!)
Делает выбранные элементы невидимыми.
def prepend(selector:String!, val:Any*)
Добавляет содержимое val
в начало контента выбранных элементов.
def remove(selector:String!)
Удаляет выбранные элементы с веб-страницы.
def removeClass(selector: String!, class: String!)
Удаляет класс у выбранных элементов.
def replace(selector:String!, val:Any*)
Заменяет выбранные элементы на содержимое val
.
def show(selector:String!)
Делает выбранные элементы видимыми.
def subst(id:String!, val:Any*)
Заменяет содержимое выбранных элементов на val
.
def toggle(selector:String!)
Переключает состояние выбранных элементов на видимый/невидимый.
def val(selector:String!, value:Any!)
Устанавливает значение выбранных элементов как val
(работает, например, на элементах input
и textarea
).
def void()
Пустая инструкция, говорящая о том, что делать ничего не надо.
def addinstr(instrset:Any*)
Метод, добавляющий одну группу инструкций к другой. Пример:
jet/subst("#output" + ckey, output). jet/subst("#total", ttk.outputstring). jet/addinstr(this.calcttk)Здесь создается цепочка из двух инструкций, к которой прицепляются инструкции, порожденные другим jet-методом –
calcttk
. Значения из instrset
, не являющиеся цепочками инструкций (экземплярами структуры com/teacode/jet/c/Nojs
) игнорируются.
По умолчанию Jet использует для задания запросов от клиента к серверу такие URL, в которых напрямую указывается имя Libretto методов, которые ответственны за обработку запросов. Этот подход очень удобен.
Однако из маркетинговых и иных соображений иногда требуется использовать "красивые" URL, обладающие структурой, отличной от внутренней структуры веб-приложения. Для таких целей Jet предлагает группу методов для определения произвольных "красивых" URL.
def path(path: String!, method:String!)
Привязывает шаблон пути path
к методу с полным именем method
. Например, определение
path("find/person/{who}", "url/findpersons")позволяет интерпретировать URL
http://mysite.com/find/Johnкак
http://mysite.com/findpersons?who=JohnИспользование
*
позволяет передавать обработчику URL любой длины. Например, под определение
path("find/{who}/*", "url/findpersons")подпадут и
http://mysite.com/find/Johnи
http://mysite.com/find/John/aaa/bbb/ccc
Коллекция путей веб-приложения создается в транзакционной памяти приложения. Метод path/2
добавляет определения в коллекцию. Для управления коллекцией добавлены еще два метода.
def cleanpath
Очищает текущую коллекцию путей. Используется, если необходимо откорректировать коллекцию.
def nopath
Истинен, если коллекция путей веб-приложения еще не создана. Как правило, используется в виде гарда перед созданием коллекции путей, чтобы избежать повторного создания.
В процессе работы веб-приложения возникает необходимость сохранения оперативной информации, например, о залогинившихся пользователях. Часть такой информации является глобальной для всех действующих сессий веб-приложения, другая часть относится к конкретной сессии. Для эффективного управления такой оперативной информацией библиотека Jet предоставляет две группы методов:
mem
и gmem
– для запоминания данных, уникальных для каждой конкретной сессии
pub
и gpub
– для сохранения данных, глобальных в рамках всего веб-приложения
Например, jet/mem('login, 'avm)
сохраняет имя пользователя текущей сессии в ключе 'login
. При этом для каждой сессии будет собственное значение ключа 'login
.
Значение ключа 'forall
после определения jet/pub('forall, 115)
будет равняться 115
для всех сессий веб-приложения.
mem
и pub
позволяют сохранять и получать значение по ключу. gmem
и gpub
позволяют организовать именованные коллекции пар ключ/значение на уровне отдельной веб-сессии и приложения в целом.
def mem(key:String!):Any*
Получение по ключу key
значения, соответствующего текущей сессии.
def mem(key:String!, value:Any*)
Сохранение значения ключа key
для текущей сессии.
def pub(key:String!):Any*
Получение по ключу key
глобального значения для всего веб-приложения.
def pub(key:String!, value:Any*)
Сохранение в ключе key
глобального значения для всего веб-приложения.
gmem(colname:String!, key:String!):Any*
Получение значения ключа key
в рамках коллекции. colname
– имя коллекции, в которой хранится значение ключа key
. Коллекция colname
уникальна для каждой веб-сессии.
gmem(colname:String!, key:String!, value:Any*)
Сохранение значения ключа в коллекции. colname
– имя коллекции, в которой сохраняется значение ключа key
. Коллекция colname
уникальна для каждой веб-сессии.
Конвенция. Если библиотеке с именем com/teacode/something
необходимо хранить свои данные уровня отдельной сессии, то она хранит их в коллекции gmem("com/teacode/something", key, value)
.
gpub(colname:String!, key:String!):Any*
Получение значения ключа key
в рамках коллекции. colname
– имя коллекции, в которой хранится значение ключа key
. Коллекция colname
– единая для всех сессий веб-приложения.
gpub(colname:String!, key:String!, value:Any*)
Сохранение значения ключа в коллекции. colname
– имя коллекции, в которой сохраняется значение ключа key
. Коллекция colname
– единая для всех сессий веб-приложения.
Конвенция. Если библиотеке с именем com/teacode/something
необходимо хранить свои данные уровня всего приложения, то она хранит их в коллекции gpub("com/teacode/something", key, value)
.
Работа с транзакционной памятью в Jet базируется на библиотеке libretto/mem
. Эта транзакционная память позволяет по-разному работать с данными уровня отдельной сессии и уровня приложения в целом. Для хранения данных уровня сессии используется корневой мэп базы libretto/mem
, доступный с помощью метода
def sesctx:mem/Map!
Для каждой сессии выделяется собственный корневой элемент, что позволяет разделять данные, относящиеся к разным сессиям. По завершении сессии данные, относящиеся к этой сессии, автоматические удаляются сборщиком мусора.
Для хранения данных, которые являются общими для всех сессий конкретного приложения, используется корневой мэп уровня приложения, доступный с помощью метода
def appctx:mem/Map!
Данные этого уровня вычищаются только при остановке приложения.
Можно выделить три уровня доступа к данным, три "области видимости":
window.localStorage
.
window.sessionStorage
.
input/hidden
и переменные javascript.
Jet предоставляет регулярные средства работы с данными этих трех уровней, хранимыми на клиенте.
По сравнению с имеющимися сегодня средствами работы с клиентской памятью (куки, объекты хранения в Javascript window.localStorage
и window.sessionStorage
) в Jet имеется два важных новшества:
input/hidden
элементов и явного использования кода на javascript.
В Jet выборка элементов веб-страницы, значение которых передается на сервер, осуществляется на языке селекторов JQuery (например, ".cls"
выбирает значения элементов класса cls
). Для организации чтения хранимых данных этот язык расширен в Jet специальными селекторами.
Использование селекторов позволяет обойтись мимимальным количеством методов управления значениями, и избавляет от необходимости добавлять аргументы методам call
, ajax
и interval
.
Селекторы для данных в хранилищах начинаются с восклицательного знака !
:
"!l:Key"
– селектор параметра в постоянной локальной памяти
"!s:Key"
– селектор параметра в сессионной памяти
"!p:Key"
– селектор параметра в страничной памяти
"!d:Key"
– селектор динамического параметра, вычисляемого в момент выборки.
Получение значений хранимых данных с клиента осуществляется, как и для других параметров. Например, для того чтобы организовать получение значения html элемента с id=="inputname"
мы пишем
call('savename, (), "#inputname")Если же необходимо получить значение некоторого локального параметра "!l:Key", то пишем:
call('savename, (), "!l:Key")
Единственное отличие состоит в том, что при получении хранимых данных обработчиком запроса используется вся строка селектора, а не только идентификатор объекта. Для inputname
имеем:
fix name = this.jet/s('inputname)Для
"!l:Key"
:
fix currentScroll = this.jet/s("!l:Key")
Значения параметров хранимых данных устанавливаются и корректируются двумя методами – store
и assign
. Например, если нам необходимо, чтобы при переходе по гиперссылке на сервер в качестве параметра передавалось текущее значение скролла, то организуем веб-страницу следующим образом:
<html> ... <body> #{jet/store("!d:Scroll", "document.body.scrollTop")} ... <a href=#call('handler, (), "!d:Scroll")>Ссылка с передачей скролла</a> ... </body> </html>Теперь при нажатии на гиперссылку значение
document.body.scrollTop
будет оцениваться непосредственно перед передачей на сервер. Заметим, что стандартными средствами html задачу передачи значения произвольного параметра на сервер решить невозможно. Приходится писать специальный javascript код на клиенте.
В нашем примере решение проблемы передачи произвольного динамического параметра от клиента к серверу берет на себя Jet.
store(key:String!, value:(String + Int + Real)!)
Определение параметра в рамках html страницы и присваивание ему значения. Например,
<body> #{jet/store("!l:Counter", 0)} ... </body>
assign(key:String!, value:(String + Int + Real)!)
Инструкция присваивания нового значения параметру, посылаемая обработчиком запроса на клиентскую сторону. Если параметра нет, то он создается.
Например,
def counterHandler = { fix counter = this.jet/i("!l:Counter") jet/assign("!l:Counter", counter+1). jet/alert("Счетчик увеличился!") }
def trand(basename: String!, expr: Block!)*
Выполняет блок (анонимную функцию) expr
в рамках транзакции в базе данных с именем basename
, управляемой библиотекой com/teacode/data
. Если базы с именем basename
нет, то база создается. Расположением базы данных управляет фреймворк.
Метод trand/2
удобно использовать для создания хендлеров конкретных баз. Например,
use com/teacode/data def mb(#expr) = trand('mybase, expr) mb: { data/root!'name = 'John }
basepath(basename:String!):String!
Получение полного пути до базы, имеющей краткое имя basename
(например, для осуществления прямого доступа к базе через метод com/teacode/data/dir / 1
).
В частности, basepath / 1
полезен для реализации библиотек, работающих с внешними базами данных в рамках веб-приложений (когда в качестве параметров могут передаваться только строковые значения).
def background // jet definition of background
// backround call on client side def bkg(prior:Int!, fun:String!) def bkg(prior:Int!, fun:String!, #constants) def bkg(prior:Int!, fun:String!, #constants, selectors:String*)
def bkgprior(prs:Int*) // config, e.g. bkg((3,2,1))
def emit // for control
def observe(millis:Int!) // html instruction catcher
def envoke(fun:String!, params:map/M!, sid:String!) // call on server side
Jet предлагает единообразную группу методов для основных операций эскейпинга, применяемых в процессе веб-программирования.
def String escXML
Эскейпинг строк в XML. Превращает
"<a>&</a>".jet/escXMLв
<a>&amp;</a>
def String escURL
Эскейпинг URL и URI. Превращает
"Всем привет".jet/escURLв
%D0%92%D1%81%D0%B5%D0%BC+%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82
def String escJS
Эскейпинг Javascript строки. Превращает
<<aaa\bbb\"'ccc>>.jet/escJSв
aaa\\bbb\\"\'ccc
def method
Получение http-метода текущего вызова. Возможные значения:'get, 'post, 'put, 'delete
– строчными буквами.
def path*
Получение path-части URL в виде последовательности строк. Например, ('a, 'b, 'c)
для http://my.com/a/b/c/?name=Tom
.
Базовый обработчик http–запросов с помощью Libretto Jet.
def sid
Получение уникального идентификатора сессии.
def ups*
Получение списка всех веб–параметров, например, ('name, 'age)
для http://my.com/?name=Tom&age=15
.
def jetjs
Метод возвращает Javascript-код, реализующий работу Libretto Jet на клиенте. Библиотека Jet использует на клиентской стороне JQuery. Поэтому и JQuery и код jetjs
должны быть загружены на веб-странице. Пример веб-страницы, готовой для работы с Jet:
use com/teacode/jet ... <html> <head> <script src="https://code.jquery.com/jquery.min.js"/> <script>#{jet/jetjs}</script> </head> <body> Hello, world! </body> </html>
def map/Map parsemap
Превращает мэп Libretto в строковое представление объекта Javascript.