Минимальное DB-GUI приложение на PicoLisp

в 10:02, , рубрики: Веб-разработка

От переводчика:
Продолжаем восполнять недостаток информации на русском языке об интереснейшем диалекте 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) ) ) ) ) )

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

Минимальное DB-GUI приложение на PicoLisp - 1

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

По соглашению, 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js