REBOL Quick Start: Part 6 - GUI Form and Submit to Server
Users often ask how to submit input data from a REBOL GUI form to a server. It's simple to do, but the method is not easy to find on REBOL websites.
This example shows how to write a REBOL-based client and server for a simple feedback form.
For those of you who just want to see the code, here it is:
- fb-example.r -- the client code, a feedback form (about 2 KB)
- fb-cgi.r -- the server code as a CGI script (less than 1 KB)
Both of these scripts have been tested with recent releases of REBOL (R2).
The Client Script
The client script implements a feedback form. This is an updated version of a script that I wrote in 2001 for users to submit feedback and comments.
You can run it directly from the REBOL prompt with the line:
or, you can download a copy of the script with:
write %fb-example.r read http://www.rebol.com/view/fb-example.r
When you run the script, it will open a window like this:
Let's break the script into separate sections for discussion.
The User Interface
The first part of the client script defines the user interface. Notice that it looks a bit different from other REBOL code that you've seen earlier. It uses all of the same notations for values, but it's arranged in a different order. What you are looking at is a dialect.
Dialects provide a major benefit in REBOL. They make you more efficient in expressing concepts within specific domains, such as user interfaces. Dialects are mini-languages that have their own grammar but use the same REBOL lexicon (the way values are written such as quoted strings, numbers, URLs, blocks, etc.) In this section of the code, the domain is that of a GUI. The dialect itself is called VID, the Visual Interface Dialect.
Looking at the various pieces of the GUI, we begin with:
gui: layout [
This line calls the layout function that converts the GUI dialect block (the VID code) into actual graphical objects called faces. The function returns the top level face which holds all of the other sub-faces, and it will be displayed in a window later in the program.
The VID block begins with a few setup and style definitions:
backeffect [gradient 0x1 220.220.240 120.120.120] across space 4x4 style lab1 lab 100 style lab2 lab 74 style field1 field 196 style field2 field 114
These act somewhat like CSS styles in HTML. They provide some local variations to the predefined face styles. For example, the lab1 line redefines the width to be 100 pixels.
Next, you will see the actual elements of the GUI. For example, the line:
lab1 "User name" f-name: field1
defines a label "User name" followed by an string input field. The field is given the name f-name so we can refer to it directly in our processing code.
You will then see:
lab2 "Product" f-product: field2 form system/product return
This is another label and input field, followed by a return word. Here, within the VID dialect, return means something different from normal code. It's meaning is similar to carriage-return used with text. That is, go to the next line.
The rest of the GUI is:
lab1 "Email address" f-email: field1 lab2 "Version" f-version: field2 form system/version return lab1 "Date" f-date: field1 form now lab2 "Urgency" f-urge: drop-down 114 data ["normal" "critical" "low" "note"] return lab1 "Summary" f-summary: field 400 return lab1 "Description" f-description: area wrap 400x72 return lab1 "Example" f-code: area 400x72 font [name: font-fixed] return lab1 btn-enter "Send" #"^S" [submit-fields] btn "Clear" [reset-fields show gui] btn-cancel #"^Q" [quit]
Those last few lines provide some buttons for submitting the form, clearing it, and cancelling it. Also note that keyboard shortcuts are provided for send and cancel.
GUI field initialization
A function is defined to set the elements of the GUI to its default values.
reset-fields: does [ unfocus set-face f-name user-prefs/name set-face f-email any [system/user/email ""] set-face f-date now set-face f-version system/version set-face f-urge first f-urge/list-data clear-face f-summary clear-face f-description clear-face f-code focus f-summary ]
This function is also used by the clear button to reset all fields:
btn "Clear" [reset-fields show gui]
When the user clicks the send button, before sending the form data to the server, we want to verify certain that fields are set correctly. The function below checks the essential fields and alerts the user if information is missing:
check-fields: does [ foreach [field name] [ f-version "version" f-summary "summary" f-description "description" ][ if empty? get-face get field [ alert reform ["The" name "field is required."] return false ] ] if all [ not empty? get-face f-email not email? try [load get-face f-email] ][ alert "The email address is not valid." return false ] true ]
Notice that the email field is not required, but if it is set, the field must be a valid email address.
Getting all fields
In order to submit the form data to the server, we must obtain all field values. This can be done by calling get-face on each GUI variable. Rather than use a list of all the field variables, the code below scans all GUI faces for those that have variable names, and builds a block of the names and their values.
get-fields: has [data] [ data: make block! 10 foreach face gui/pane [ if face/var [ repend data [face/var get-face face] ] ] data ]
For debugging, to see what the result block looks like, just add:
Submitting to server
There are various protocols to send the form data to a server. The most common methods are:
- Sending messages using TCP/IP ports directly
- Using the REBOL/Services protocol
- Using the CGI mechanism supported by HTTP
Unless you have your own dedicated server, the first method is likely to be restricted by your ISP. The second is overkill for simple scripts like this one. So, we'll use the third method, which ISPs generally support (as long as they let you run REBOL CGI scripts.)
Two functions are used to submit the form data to the server.
The submit-fields function drives the process:
submit-fields: has [data result] [ unless check-fields [exit] result: send-server get-fields either result/1 = 'ok [ alert "Feedback has been sent" quit ][ alert reform ["Server error:" result] ] ]
If the server returns ok, the program displays a successful result and quits. Otherwise, the program displays an error message and allows the user to make corrections to any fields that may be a problem.
To send the data block to the server and get a block response back from the server we use the function below.
send-server: func [data /local result] [ data: compress mold/only data flash "Contacting server..." result: attempt [read/custom fb-url reduce ['post data]] unview any [attempt [load/all result] [failed connection]] ]
This is a handy function; one I use in many different programs. It's worth learning. The function takes a block, molds it into a string (of valid REBOL source), compresses it, then sends the resulting binary data to the server. The server processes the data (as will be shown below) then replies with a REBOL block, or has an error.
To debug what the server is returning, just add a:
after the read line.
The Server Script
We start with a simple HTTP header:
print "content-type: text/plain^/"
Reading the input data
Then we read the POST data that was sent by the client:
data: to-binary read-cgi/limit 50'000 data: load/all decompress data
I should mention that you could write that all in one line:
data: load/all decompress to-binary read-cgi/limit 50'000
But, as two separate lines, you can put some debugging output in between if necessary (useful if you see decompression errors.)
Verifying the data
Next, the script verifies that the essential fields are present, and returns an error if there's a problem.
foreach field [ f-product f-version f-summary f-description ][ unless value: select data field [ print ['missing field] quit ] if empty? value [ print ['empty field] quit ] ]
Sending back a response
If everything passes, the script appends the data to a storage file for pickup by another script at a later time:
write/append %fb-messages.r append mold data newline
Finally, an ok is sent back to the client to indicate success: