Minimal DB / GUI application on PicoLisp

Original author: Alexander Burger
  • Transfer
  • Tutorial
From a translator: We
continue to make up for the lack of information in Russian about the most interesting dialect Lisp. Previous article: Web Application Development in PicoLisp Project
Home Page: http://picolisp.com
A few weeks ago, my wife asked for a small application - an online database for the addresses and contact details of family members, relatives, friends, and so on.

Typically, in PicoLisp a database contains objects of various classes. To process them in the GUI, there should be the ability to search, create and delete objects, and edit their properties.

A typical PicoLisp application implements the following features:
  • Menu item for each type of object or function
  • The search dialog box that appears when you select a menu item
  • Search by criteria in the dialog box
  • Then click on the found object or on the "New" button to create a new object
  • A form will appear where you can edit this object.
  • The form has a delete button to remove this object.

But with a simple address database this is unnecessary. There is only one class of objects. Fortunately, there is an easy way. Firstly, there is no need for a menu. We can go directly to the addresses. Everything else can be treated with a GUI component +QueryChart.

Full listing at the end of the article.

Data model

Define the "Person" class (for the purposes of this article in a slightly abbreviated form) as:
   (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
If necessary, the class can be easily extended.

Instead of individual properties for street, zip, city, etc. we have one property in free format for the full address. We will define two non-unique indexes, one for the username and one for the address. The "name" index supports fuzzy searches (using the Soundex algorithm for similar names).

GUI Functions

We have only one GUI function called work. It starts with standard functions app(= set up a session), action(= handle event form) and html(= generate HTML pages), which is typical for every PicoLisp application. An optional function uses JavaScript to create a keep-alive event.
   (de work ()
      (app)
      (action
         (html 0 Ttl "@lib.css" NIL
            ( 2)
Then, as an elementary security measure, it shows the password field in the first form, with the password "mypass" hard-wired.
            (ifn *Login
               (form NIL
                  (gui 'pw '(+PwField) 20 ,"Password")
                  (gui '(+Button) ,"login"
                     '(ifn (= "mypass" (val> (: home pw)))
                        (error ,"Permission denied")
                        (on *Login)
                        (url "!work") ) ) )
(The real application uses full user / password authentication (using the lib / adm.l library). We omitted it here for brevity only)

In any case, it uses the global * Login variable, which is set by pressing the login button in case password matches. In this case, the second, main form is displayed.
               (form NIL
                  ( "--."
                     "Name" (gui 'nm '(+DbHint +TextField) '(nm +Prs) 20)
                     (searchButton '(init> (: home query)))
                     "Address" (gui 'adr '(+DbHint +TextField) '(adr +Prs) 20)
                     (resetButton '(nm adr query)) )
It displays two search fields, “Name” and “Address”, and two buttons “Search” and “Reset”. Search fields use the prefix class + DbHint to display a drop-down list with matching names that already exist in the database. The Reset button clears all fields.

Now let's move on to the "heart" of the GUI of this application. We use the class +QueryChartto search for records, as well as to create and edit them.

+QueryChartused in all search dialogs. It uses a Pilog query to search for a given set of criteria, and displays an unlimited number of results, as long as there are suitable elements, and the user continues to press the scroll buttons.
                  (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) ) ) )
The first argument (here 12) gives the initial number of matches to populate the table. The second argument is a Pilog request, which uses the values ​​of the search fields “Name” and “Address” for a fuzzy and partial search. See http://software-lab.de/doc/select.html for details.

Then three standard arguments for the class follow.+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)) ) ) ) )
namely: the number of columns (6 here) and the function putand get.

The class +Chartcalls these functions whenever something happens in the GUI. The put-function converts the logical content of the table row (here the address of the object) into the physical display of the name, address, email, etc.:
                     '((This) (list (: nm) (: adr) (: em) (: tel) (: dob)))
The argument Thisfor the put function is an object, and it expands into a list of values ​​for the table row.

get-function performs the opposite action, translating the values ​​in the string into the properties of the object. It takes into the Llist of values ​​from the GUI (strings, numbers, dates, etc. entered by the user), and in D- the address of the object in the database.
                     '((L D)
Then it checks condwhether the object exists in the expression D. If yes, it saves the values ​​from Lin the properties of the corresponding object, thus updating the database as necessary:
                           (D
                              (mapc
                                 '((K V) (put!> D K V))
                                 '(nm adr em tel dob)
                                 L )
                              D )
If the object does not exist, but the first column of the table contains the name (which the user just entered), then a new object is created in the database with this name:
                           ((car L)
                              (new! '(+Prs) 'nm (car L)) ) ) ) )
That's all! This is all the logic needed to create and edit records.

+Chartor +QueryChart- an internal object that implements the logic of this graphical interface. Now we need physical components to interact with the user. Put them in a table
                  ( NIL (choTtl "Entries" '+Prs)
с соответствующими заголовками
                     (quote
                        (NIL "Name")
                        (NIL "Address")
                        (NIL "E-Mail")
                        (NIL "Telephone")
                        (NIL "Date of birth") )
следом идут 12 строк с полями текста, email и телефона
                     (do 12
                        ( 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
            ( 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
                  ( "--."
                     "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)) ) ) ) )
                  (
NIL (choTtl "Entries" '+Prs) (quote (NIL "Name") (NIL "Address") (NIL "E-Mail") (NIL "Telephone") (NIL "Date of birth") ) (do 12 ( 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

Also popular now: