От переводчика:Несколько недель назад моя жена попросила небольшое приложение — онлайновую базу данных для адресов и контактных данных членов семьи, родственников, друзей и так далее.
Продолжаем восполнять недостаток информации на русском языке об интереснейшем диалекте 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. Затем, в качестве меры элементарной безопасности, она показывает поле для ввода пароля в первой форме, с жестко зашитым паролем "mypass".(de work () (app) (action (html 0 Ttl "@lib.css" NIL (<ping> 2)
(Реальное приложение использует полноценную аутентификацию по пользователю/паролю (с помощью библотеки «lib/adm.l»). Мы опустили ее здесь только для краткости)(ifn *Login (form NIL (gui 'pw '(+PwField) 20 ,"Password") (gui '(+Button) ,"login" '(ifn (= "mypass" (val> (: home pw))) (error ,"Permission denied") (on *Login) (url "!work") ) ) )
В любом случае она использует глобальную переменную *Login, которая устанавливается нажатием кнопки «login» в случае совпадения пароля. В этом случае отображается вторая, главная форма.
Она отображает два поисковых поля, «Имя» и «Адрес», и две кнопки «Поиск» и «Сброс». Поля поиска используют префикс-класс +DbHint для отображения выпадающего списка с подходящими именами, уже имеющимися в БД. Кнопка «Сброс» очищает все поля.(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 этого приложения. Мы используем класс
+QueryChart как для поиска записей, так и для их создания и редактирования.+QueryChart используется во всех диалогах поиска. Он использует Pilog-запрос для поиска по заданному набору критериев, и отображает возможно неограниченное количество результатов, пока есть подходящие элементы, и пользователь продолжает нажимать кнопки прокрутки. Первый аргумент (здесь 12) дает начальное количество совпадений для заполнения таблицы. Второй аргумент — Pilog-запрос, который использует значения поисковых полей «Имя» и «Адрес» для нечеткого и частичного поиска. Смотрите http://software-lab.de/doc/select.html для подробной информации.(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) ) ) )
Затем следуют три стандартных аргумента для класса
+Chartа именно: количество столбцов (здесь 6) и функции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)) ) ) ) )
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)
следом идут 12 строк с полями текста, email и телефона(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)) ) ) ) )
+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
