REBOL
Docs Blog Get-it

REBOL Quick Start: Part 6 - GUI Form and Submit to Server

By Carl Sassenrath
REBOL's Designer
Revised: 12-Mar-2024

The Plan

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.

The Code

For those of you who just want to see the code, here it is:

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:

do http://data.rebol.com/view/fb-example.r

or, you can download a copy of the script with:

write %fb-example.r read http://data.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]

Checking fields

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:

?? data

Submitting to server

There are various protocols to send the form data to a server. The most common methods are:

  1. Sending messages using TCP/IP ports directly
  2. Using the REBOL/Services protocol
  3. 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.

At the center of this is a read that uses the /custom refinement to send an HTTP POST data request to the webserver. Examine that line closely and don't forget the reduce.

To debug what the server is returning, just add a:

?? result

after the read line.

Useful hint

For debugging, it is useful to know that you can test the send-server function directly with a line such as:

probe send-server [test "this"]

In this case, because the block is not valid feedback data, you will see an error message return from the server.

The Server Script

On the server side we use fb-cgi.r, a CGI script like those discussed in the CGI Tutorial.

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

The read-cgi function is defined in newer versions of REBOL, but older versions can use this code: A Handy CGI Read Function.

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:

print 'ok
Important note

The CGI script shown here is just an example. It's useful to use privately by yourself or your organization. If you want to use it publicly for anyone, then there are various features you'll want to add to validate users, detect large submissions, filter out miscellaneous abusive postings. Those features are beyond the scope of this tutorial.

About | Contact | PrivacyREBOL Technologies 2024