От переводчика:
Продолжаем восполнять недостаток информации на русском языке об интереснейшем диалекте Lisp. Предыдущая статья: Разработка веб-приложений в PicoLisp
Домашняя страничка проекта: http://picolisp.com
Несколько недель назад моя жена попросила небольшое приложение — онлайновую базу данных для адресов и контактных данных членов семьи, родственников, друзей и так далее.
Как правило, в PicoLisp база данных содержит объекты различных классов. Для их обработки в GUI должна быть возможность для поиска, создания и удаления объектов, редактирования их свойств.
Типичное PicoLisp-приложение реализует следующие возможности:
- Пункт меню для каждого типа объекта или функции
- Диалоговое окно поиска, которое появляется при выборе пункта меню
- Поиск по нужным критериям в диалоговом окне
- Затем щелкните на найденный объект или на кнопку "Новый" для создания нового объекта
- Появится форма, где вы можете редактировать этот объект
- Форма имеет кнопку "Удалить", чтобы удалить этот объект
Но в случае простой базы данных адресов это излишне. Есть только один класс объектов. К счастью, есть простой способ. Во-первых, нет необходимости для меню. Мы можем перейти непосредственно к адресам. Всё остальное можно обработать одним компонентом GUI, +QueryChart
.
Полный листинг в конце статьи.
Модель данных
Определим класс "Person" (для целей данной статьи в слегка сокращенной форме) как:
(class +Prs +Entity)
(rel nm (+Sn +IdxFold +String)) # Name
(rel adr (+IdxFold +String)) # Address
(rel em (+String)) # E-Mail
(rel tel (+String)) # Telephone
(rel dob (+Date)) # Date of birth
При необходимости класс можно легко расширить.
Вместо отдельных свойств для улицы, индекса, города и т.д. у нас есть одно свойство в свободном формате для полного адреса. Мы определим два неуникальных индекса, один для имени пользователя и один для адреса. Индекс "name" поддерживает нечеткий поиск (с использованием алгоритма Soundex для похожих имен).
Функции графического интерфейса
У нас есть только одна GUI-функция под названием work
. Она начинается со стандартных функций app
(= установить сессию), action
(= обработка событий формы) и html
(= генерировать HTML-страницы), что типично для каждого приложения PicoLisp. Необязательная функция <ping>
использует JavaScript для создания события keep-alive.
(de work ()
(app)
(action
(html 0 Ttl "@lib.css" NIL
(<ping> 2)
Затем, в качестве меры элементарной безопасности, она показывает поле для ввода пароля в первой форме, с жестко зашитым паролем "mypass".
(ifn *Login
(form NIL
(gui 'pw '(+PwField) 20 ,"Password")
(gui '(+Button) ,"login"
'(ifn (= "mypass" (val> (: home pw)))
(error ,"Permission denied")
(on *Login)
(url "!work") ) ) )
(Реальное приложение использует полноценную аутентификацию по пользователю/паролю (с помощью библотеки «lib/adm.l»). Мы опустили ее здесь только для краткости)
В любом случае она использует глобальную переменную *Login, которая устанавливается нажатием кнопки «login» в случае совпадения пароля. В этом случае отображается вторая, главная форма.
(form NIL
(<grid> "--."
"Name" (gui 'nm '(+DbHint +TextField) '(nm +Prs) 20)
(searchButton '(init> (: home query)))
"Address" (gui 'adr '(+DbHint +TextField) '(adr +Prs) 20)
(resetButton '(nm adr query)) )
Она отображает два поисковых поля, «Имя» и «Адрес», и две кнопки «Поиск» и «Сброс». Поля поиска используют префикс-класс +DbHint для отображения выпадающего списка с подходящими именами, уже имеющимися в БД. Кнопка «Сброс» очищает все поля.
Теперь перейдем к "сердцу" GUI этого приложения. Мы используем класс+QueryChart
как для поиска записей, так и для их создания и редактирования.
+QueryChart
используется во всех диалогах поиска. Он использует Pilog-запрос для поиска по заданному набору критериев, и отображает возможно неограниченное количество результатов, пока есть подходящие элементы, и пользователь продолжает нажимать кнопки прокрутки.
(gui 'query '(+QueryChart) 12
'(goal
(quote
@Nm (val> (: home nm))
@Adr (val> (: home adr))
(select (@@)
((nm +Prs @Nm) (adr +Prs @Adr))
(tolr @Nm @@ nm)
(part @Adr @@ adr) ) ) )
Первый аргумент (здесь 12) дает начальное количество совпадений для заполнения таблицы. Второй аргумент — Pilog-запрос, который использует значения поисковых полей «Имя» и «Адрес» для нечеткого и частичного поиска. Смотрите http://software-lab.de/doc/select.html для подробной информации.
Затем следуют три стандартных аргумента для класса +Chart
6
'((This) (list (: nm) (: adr) (: em) (: tel) (: dob)))
'((L D)
(cond
(D
(mapc
'((K V) (put!> D K V))
'(nm adr em tel dob)
L )
D )
((car L)
(new! '(+Prs) 'nm (car L)) ) ) ) )
а именно: количество столбцов (здесь 6) и функции put
и get
.
Класс +Chart
вызывает эти функции всякий раз, когда что-то происходит в GUI. put-функция преобразует логическое содержание строки таблицы (здесь адрес объекта) в физическое отображение имени, адреса, email-а и т.д.:
'((This) (list (: nm) (: adr) (: em) (: tel) (: dob)))
Аргумент This
для put-функции — объект, и он разворачивается в список значений для строки таблицы.
get
-функция выполняет обратное действие, транслируя значения в строке в свойства объекта. Она принимает в L
список значений из GUI (строки, числа, даты и т.п., введенные пользователем), а в D
— адрес объекта в БД.
'((L D)
Затем она проверяет, в выражении cond
, существует ли объект D
.Если да, она сохраняет значения из L
в свойствах соответствующего объекта, обновляя таким образом БД по необходимости:
(D
(mapc
'((K V) (put!> D K V))
'(nm adr em tel dob)
L )
D )
Если объект не существует, но первая колонка таблицы содержит имя (которое пользователь только что ввёл),то создается новый объект в БД с этим именем:
((car L)
(new! '(+Prs) 'nm (car L)) ) ) ) )
Вот и всё! Это вся логика, необходимая для создания и редактирования записей.
+Chart
или +QueryChart
— внутренний объект, реализующий логику этого графического интерфейса.Теперь нам нужны физические компоненты для взаимодействия с пользователем.Поместим их в таблицу
(<table> NIL (choTtl "Entries" '+Prs)
с соответствующими заголовками
(quote
(NIL "Name")
(NIL "Address")
(NIL "E-Mail")
(NIL "Telephone")
(NIL "Date of birth") )
следом идут 12 строк с полями текста, email и телефона
(do 12
(<row> NIL
(gui 1 '(+TextField) 30)
(gui 2 '(+TextField) 40)
(gui 3 '(+MailField) 20)
(gui 4 '(+TelField) 15)
(gui 5 '(+DateField) 10)
(gui 6 '(+DelRowButton)
'(lose!> (curr))
'(text "Delete Entry @1?" (curr 'nm)) ) ) ) )
Обратите внимание на кнопку +DelRowButton
в последней колонке.Она может быть использована для удаления записи из БД. Она вызывает диалог с подтверждением, действительно ли пользователь хочет удалить запись. Впрочем, при удалении нескольких строк, она не будет запрашивать пользователя в следующий раз.
И в самом конце отображаются четыре стандартные кнопки прокрутки
(scroll 12) ) ) ) ) )
Они позволяют построчно и постранично прокручивать содержимое таблицы.
Инициализация и запуск
По соглашению, PicoLisp-приложение предоставляет две функции, main
и go
.Функция main
должна инициализировать рабочее окружение, а go
должна запускать цикл обработки событий GUI.
(de main ()
(locale "UK")
(pool "adr.db") )
(de go ()
(server 8080 "!work") )
locale
главным образом нужен для правильной обработки поля +TelField
с номерами телефонов. Вы можете предоставить свои настройки локализации в каталоге loc/
.
Если вы скопируете код ниже в файл "minDbGui.l" или скачаете с http://software-lab.de/minDbGui.l, можете запустить его таким способом:
$ pil minDbGui.l -main -go -wait
либо в режиме отладки:
$ pil minDbGui.l -main -go +
Исходный код программы
# 11jan15abu
# (c) Software Lab. Alexander Burger
(allowed ()
"!work" "@lib.css" )
(load "@lib/http.l" "@lib/xhtml.l" "@lib/form.l")
(class +Prs +Entity)
(rel nm (+Sn +IdxFold +String)) # Name
(rel adr (+IdxFold +String)) # Address
(rel em (+String)) # E-Mail
(rel tel (+String)) # Telephone
(rel dob (+Date)) # Date of birth
(de work ()
(app)
(action
(html 0 Ttl "@lib.css" NIL
(<ping> 2)
(ifn *Login
(form NIL
(gui 'pw '(+PwField) 20 ,"Password")
(gui '(+Button) ,"login"
'(ifn (= "mypass" (val> (: home pw)))
(error ,"Permission denied")
(on *Login)
(url "!work") ) ) )
(form NIL
(<grid> "--."
"Name" (gui 'nm '(+DbHint +TextField) '(nm +Prs) 20)
(searchButton '(init> (: home query)))
"Address" (gui 'adr '(+DbHint +TextField) '(adr +Prs) 20)
(resetButton '(nm adr query)) )
(gui 'query '(+QueryChart) 12
'(goal
(quote
@Nm (val> (: home nm))
@Adr (val> (: home adr))
(select (@@)
((nm +Prs @Nm) (adr +Prs @Adr))
(tolr @Nm @@ nm)
(part @Adr @@ adr) ) ) )
6
'((This) (list (: nm) (: adr) (: em) (: tel) (: dob)))
'((L D)
(cond
(D
(mapc
'((K V) (put!> D K V))
'(nm adr em tel dob)
L )
D )
((car L)
(new! '(+Prs) 'nm (car L)) ) ) ) )
(<table> NIL (choTtl "Entries" '+Prs)
(quote
(NIL "Name")
(NIL "Address")
(NIL "E-Mail")
(NIL "Telephone")
(NIL "Date of birth") )
(do 12
(<row> NIL
(gui 1 '(+TextField) 30)
(gui 2 '(+TextField) 40)
(gui 3 '(+MailField) 20)
(gui 4 '(+TelField) 15)
(gui 5 '(+DateField) 10)
(gui 6 '(+DelRowButton)
'(lose!> (curr))
'(text "Delete Entry @1?" (curr 'nm)) ) ) ) )
(scroll 12) ) ) ) ) )
(de main ()
(locale "UK")
(pool "adr.db") )
(de go ()
(server 8080 "!work") )
# vi:et:ts=3:sw=3
Автор: mmans