📜 ⬆️ ⬇️

Minimum DB / GUI application on PicoLisp

From the translator:
We continue to fill the lack of information in Russian about the most interesting Lisp dialect. 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 of addresses and contact information for family members, relatives, friends, and so on.

As a rule, in PicoLisp the database contains objects of various classes. To process them in the GUI, it must be possible to search for, create and delete objects, edit their properties.

A typical PicoLisp application implements the following features:
But in the case of a simple address database, this is unnecessary. There is only one class of objects. Fortunately, there is a simple way. First, there is no need for a menu. We can go directly to the addresses. Everything else can be processed by a single GUI component, +QueryChart .
')
Full listing at the end of the article.

Data model

We define the class "Person" (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 expanded.

Instead of separate properties for the street, index, 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 search (using the Soundex algorithm for similar names).

GUI functions

We have only one GUI function called work . It starts with the standard functions app (= set session), action (= form event handling) and html (= generate HTML pages), which is typical for each PicoLisp application. The optional <ping> function uses JavaScript to create a keep-alive event.
  (de work () (app) (action (html 0 Ttl "@lib.css" NIL (<ping> 2) 
Then, as an elementary security measure, it displays 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 lib / adm.l libraries). We omitted it here only for brevity)

In any case, it uses the * Login global variable, which is set by pressing the “login” button in case the password matches. In this case, the second main form is displayed.
  (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)) ) 
It displays two search fields, "Name" and "Address", and two buttons "Search" and "Reset". Search fields use the + DbHint prefix class to display a drop-down list with matching names already available in the database. The "Reset" button clears all fields.

We now turn to the "heart" of the GUI of this application. We use the +QueryChart class both for searching for records, and for creating and editing them.

+QueryChart used in all search dialogs. It uses the Pilog query to search for a given set of criteria, and displays a possible 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 fill the table. The second argument is the Pilog query, which uses the values ​​of the Name and Address search fields for fuzzy and partial searches. See http://software-lab.de/doc/select.html for details.

Then follow the three standard arguments for the +Chart class +Chart
  6 '((This) (list (: nm) (: adr) (: em) (: tel) (: dob))) '((LD) (cond (D (mapc '((KV) (put!> DKV)) '(nm adr em tel dob) L ) D ) ((car L) (new! '(+Prs) 'nm (car L)) ) ) ) ) 
namely, the number of columns (here 6) and the function put and get .

The +Chart class calls 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) to the physical display of the name, address, email, etc.:
  '((This) (list (: nm) (: adr) (: em) (: tel) (: dob))) 
The argument for a put-function is an object, and it expands to a list of values ​​for a row in a table.

get function performs the opposite action, translating the values ​​in the string to the properties of the object. It accepts in L list of values ​​from the GUI (strings, numbers, dates, etc., entered by the user), and in D address of the object in the database.
  '((LD) 
It then checks in the cond expression whether the object D exists. If so, it stores the values ​​from L in the properties of the corresponding object, thus updating the database as necessary:
  (D (mapc '((KV) (put!> DKV)) '(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 has just entered), 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.

+Chart or +QueryChart is an internal object that implements the logic of this graphical interface +QueryChart we need physical components to interact with the user +QueryChart them in a table
  (<table> NIL (choTtl "Entries" '+Prs) 
with appropriate headings
  (quote (NIL "Name") (NIL "Address") (NIL "E-Mail") (NIL "Telephone") (NIL "Date of birth") ) 
12 lines follow with fields of text, email and phone
  (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)) ) ) ) ) 
Note the +DelRowButton button in the last column. It can be used to delete a record from the database. It brings up a dialog confirming whether the user really wants to delete the entry. However, if you delete multiple lines, it will not prompt the user next time.

And at the very end, four standard scroll buttons are displayed.
  (scroll 12) ) ) ) ) ) 
They allow scrolling table contents line by page and page by page.

Initialization and launch

By convention, the PicoLisp application provides two functions, main and go . The main function should initialize the working environment, and go should start the GUI event loop.
 (de main () (locale "UK") (pool "adr.db") ) (de go () (server 8080 "!work") ) 
locale is mainly needed for correct processing of the +TelField field with phone numbers. You can provide your localization settings in the loc/ directory.

If you copy the code below into the file "minDbGui.l" or download it from http://software-lab.de/minDbGui.l , you can run it in this way:
  $ pil minDbGui.l -main -go -wait 
either in debug mode:
  $ pil minDbGui.l -main -go + 


Source code of the program
  # 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))) '((LD) (cond (D (mapc '((KV) (put!> DKV)) '(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 

Source: https://habr.com/ru/post/247965/


All Articles