note
description : "[
This example demonstrates the use of the `wsf_html` library for web form handling.
To simplify the code, it is also using the `WSF_RESPONSE.send (WSF_RESPONSE_MESSAGE)`,
thus no need to write the expected http header here.
The current code is a web interface form, returning html page as response.
notes:
* It is not using the `WSF_ROUTER` component to keep the example as simple as possible.
* It is also possible to use the `WSF_REQUEST.form_parameter (...)` directly,
but WSF_FORM provides advanced processing.
* For a CRUD REST API, you can also use `WSF_FORM` to analyze the incoming POST values,
however the html generation may be too verbose for a simple REST api.
warning: depending on your system and connector, you may need to use `WSF_REQUEST.set_uploaded_file_path (...)`
to tell the server where to store the temporary uploaded files.
(this should be a directory with write permission for the server).
For that
- inherit from WSF_REQUEST_EXPORTER
- in `execute` add at the beginning the call `request.set_uploaded_file_path (path-to-wanted-directory)`
]"
date: "$Date$"
revision: "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
local
msg: WSF_RESPONSE_MESSAGE
do
if request.path_info.same_string ("/form") then
if request.is_post_request_method then
msg := form_submission_message
elseif request.is_get_request_method then
msg := form_message
else
create {WSF_METHOD_NOT_ALLOWED_RESPONSE} msg.make (request)
end
else
msg := welcome_message --| Alternative: return a not found message.
end
response.send (msg)
end
welcome_message: WSF_HTML_PAGE_RESPONSE
local
l_html: STRING
do
Result := new_html_page_message ("Welcome")
create l_html.make_empty
l_html.append ("
Welcome
%N")
l_html.append ("Please fill this form.
")
Result.set_body (l_html)
end
form_message: WSF_HTML_PAGE_RESPONSE
local
f: WSF_FORM
l_html: STRING
do
Result := new_html_page_message ("Form")
create l_html.make_empty
l_html.append ("Fill the form
%N")
-- Build the Web form, and then generate the html into `l_html`
f := new_form
f.append_to_html (wsf_theme, l_html)
Result.set_body (l_html)
end
form_submission_message: WSF_HTML_PAGE_RESPONSE
local
f: WSF_FORM
l_html: STRING
do
create l_html.make_empty
Result := new_html_page_message ("Form")
-- Build the empty Web form
f := new_form
-- And fill it using the `request: WSF_REQUEST` object.
f.process (request, Void, Void)
-- Get the filled analyzed data from `f.last_data: WSF_FORM_DATA`
if attached f.last_data as fd then
if attached fd.errors as lst then
-- The web form has validation errors
l_html.append ("Fill the form
%N")
fd.apply_to_associated_form
across
lst as ic
loop
if attached {WSF_FORM_FIELD} ic.item.field as l_field then
l_html.append ("ERROR on field [" + l_field.name + "]")
end
end
f.append_to_html (wsf_theme, l_html)
elseif
attached fd.string_item (field_id_uuid) as l_uuid and then
attached fd.string_item (field_id_first_name) as l_firstname and then
attached fd.string_item (field_id_last_name) as l_lastname
then
-- No validation errors
-- and we have the expected values.
check
-- see `set_is_required (True)` in `new_form`.
required_value_set: not l_firstname.is_whitespace and not l_lastname.is_whitespace
end
--| Process the value to store in database, or anything ...
--| here, we just display the value in the page.
l_html.append ("Form: results
")
l_html.append ("Thank you %"" + l_firstname + " " + l_lastname + "%" for your submission ("+ l_uuid.as_string_8 +").
")
l_html.append ("")
save_form_results (l_uuid, fd)
l_html.append ("Fill again a new form.
")
else
l_html.append ("Fill the form
%N")
-- Missing form value!
fd.apply_to_associated_form
l_html.append ("ERROR: missing at least one field value !")
f.append_to_html (wsf_theme, l_html)
end
end
Result.set_body (l_html)
end
feature -- Implementation
new_html_page_message (a_title: READABLE_STRING_32): WSF_HTML_PAGE_RESPONSE
do
create Result.make
-- The following style is in this example embedded, however for bigger style, it would be recommended to include the css style as a url.
Result.head_lines.extend ("[
]")
Result.set_title (a_title)
end
new_form: WSF_FORM
-- New form object.
local
l_set: WSF_FORM_FIELD_SET
l_div: WSF_FORM_DIV
l_hidden: WSF_FORM_HIDDEN_INPUT
l_text_field: WSF_FORM_TEXT_INPUT
l_birthday_field: WSF_FORM_DATE_INPUT
l_email_field: WSF_FORM_EMAIL_INPUT
l_submit: WSF_FORM_SUBMIT_INPUT
l_radio: WSF_FORM_RADIO_INPUT
l_checkbox: WSF_FORM_CHECKBOX_INPUT
l_textarea_field: WSF_FORM_TEXTAREA
l_file_field: WSF_FORM_FILE_INPUT
l_select: WSF_FORM_SELECT
l_select_opt: WSF_FORM_SELECT_OPTION
do
create Result.make ("/form", "a-form-id")
Result.set_method_post
Result.set_multipart_form_data_encoding_type
create l_hidden.make_with_text (field_id_uuid, (create {UUID_GENERATOR}).generate_uuid.out)
Result.extend (l_hidden)
create l_set.make
l_set.set_legend ("Personal information")
Result.extend (l_set)
create l_div.make
l_div.add_css_class ("horizontal")
l_set.extend (l_div)
-- First name
create l_text_field.make (field_id_first_name)
l_text_field.set_label ("First name")
l_text_field.set_placeholder ("Enter your first name")
l_text_field.set_size (60)
l_text_field.set_is_required (True)
l_set.extend (l_text_field)
-- Last name
create l_text_field.make (field_id_last_name)
l_text_field.set_label ("Last name")
l_text_field.set_placeholder ("Enter your last name")
l_text_field.set_size (60)
l_text_field.set_is_required (True)
l_set.extend (l_text_field)
-- Birthday
create l_birthday_field.make (field_id_birthday)
l_birthday_field.set_label ("Birthday")
l_birthday_field.set_placeholder ("YYYY-MM-DD")
l_birthday_field.set_description ("Enter the date using the YYYY-MM-DD format.")
l_birthday_field.set_validation_action (agent (fd: WSF_FORM_DATA)
do
if attached fd.string_item (field_id_birthday) as str then
if is_valid_yyyy_mm_dd_date (str) then
-- Ok
else
fd.report_invalid_field (field_id_birthday, "Invalid date format!")
end
end
end)
l_set.extend (l_birthday_field)
-- Contact information
create l_set.make
l_set.set_legend ("Contact information")
Result.extend (l_set)
-- Email
create l_email_field.make (field_id_email)
l_email_field.set_label ("Primary email")
l_set.extend (l_email_field)
create l_div.make
l_div.add_css_class ("horizontal") -- See the css style, this is a way to have simple column layout in form
Result.extend (l_div)
-- Radio
create l_set.make
l_set.set_legend ("Priority")
l_div.extend (l_set)
create l_radio.make_with_value (field_id_priority, "high")
l_radio.set_label ("High")
l_set.extend (l_radio)
create l_radio.make_with_value (field_id_priority, "medium")
l_radio.set_label ("Medium")
l_set.extend (l_radio)
create l_radio.make_with_value (field_id_priority, "low")
l_radio.set_label ("Low")
l_set.extend (l_radio)
-- checkbox
create l_set.make
l_set.set_legend ("Tags")
l_set.add_css_class ("horizontal")
l_div.extend (l_set)
create l_checkbox.make_with_value (field_id_tags + "[]", "application")
l_checkbox.set_label ("Application")
l_set.extend (l_checkbox)
create l_checkbox.make_with_value (field_id_tags + "[]", "library")
l_checkbox.set_label ("Library")
l_set.extend (l_checkbox)
create l_checkbox.make_with_value (field_id_tags + "[]", "testing")
l_checkbox.set_label ("Testing")
l_set.extend (l_checkbox)
-- select
create l_set.make
l_div.extend (l_set)
create l_select.make (field_id_month)
l_select.set_label ("Choose a month")
l_set.extend (l_select)
create l_select_opt.make ("01", "January"); l_select.add_option (l_select_opt)
create l_select_opt.make ("02", "February"); l_select.add_option (l_select_opt)
create l_select_opt.make ("03", "March"); l_select.add_option (l_select_opt)
create l_select_opt.make ("04", "April"); l_select.add_option (l_select_opt)
create l_select_opt.make ("05", "May"); l_select.add_option (l_select_opt)
create l_select_opt.make ("06", "June"); l_select.add_option (l_select_opt)
create l_select_opt.make ("07", "July"); l_select.add_option (l_select_opt)
create l_select_opt.make ("08", "August"); l_select.add_option (l_select_opt)
create l_select_opt.make ("09", "September"); l_select.add_option (l_select_opt)
create l_select_opt.make ("10", "October"); l_select.add_option (l_select_opt)
create l_select_opt.make ("11", "November"); l_select.add_option (l_select_opt)
create l_select_opt.make ("12", "December"); l_select.add_option (l_select_opt)
-- Note
create l_textarea_field.make (field_id_comment)
l_textarea_field.set_label ("Post your comment")
l_textarea_field.set_cols (60)
l_textarea_field.set_rows (10)
l_textarea_field.set_description ("Please leave a comment ...")
Result.extend (l_textarea_field)
-- File
create l_file_field.make (field_id_attached_file)
l_file_field.set_label ("Attach a file")
Result.extend (l_file_field)
create l_submit.make_with_text ("op", "Submit")
Result.extend (l_submit)
end
feature -- Constants
field_id_uuid: STRING = "uuid"
field_id_first_name: STRING = "first_name"
field_id_last_name: STRING = "last_name"
field_id_birthday: STRING = "birthday"
field_id_email: STRING = "email"
field_id_priority: STRING = "priority"
field_id_tags: STRING = "tags"
field_id_month: STRING = "month"
field_id_comment: STRING = "comment"
field_id_attached_file: STRING = "attached_file"
feature {NONE} -- Helpers
save_form_uploaded_file (a_uuid: READABLE_STRING_GENERAL; a_file: WSF_UPLOADED_FILE)
local
p: PATH
d: DIRECTORY
do
p := (create {PATH}.make_current).extended ("forms").extended (a_uuid).extended ("files")
create d.make_with_path (p)
if not d.exists then
d.recursive_create_dir
end
p := p.extended (a_file.safe_filename)
if a_file.move_to (p.name) then
-- Ok file, saved.
else
-- Failed to move the uploaded file!
end
end
save_form_results (a_uuid: READABLE_STRING_GENERAL; fd: WSF_FORM_DATA)
local
p: PATH
d: DIRECTORY
f: PLAIN_TEXT_FILE
conv: UTF_CONVERTER
l_is_first: BOOLEAN
do
p := (create {PATH}.make_current).extended ("forms").extended (a_uuid)
create d.make_with_path (p)
if not d.exists then
d.recursive_create_dir
end
create f.make_with_path (p.extended ("results.json"))
if not f.exists or f.is_access_writable then
f.create_read_write
l_is_first := True
f.put_character ('{')
across
fd as ic
loop
if l_is_first then
l_is_first := False
else
f.put_character (',')
end
f.put_character ('"')
f.put_string (conv.utf_32_string_to_utf_8_string_8 (ic.key))
f.put_character ('"')
f.put_character (':')
if attached ic.item as obj then
save_value_as_json_string (obj, f, a_uuid)
else
f.put_string ("Null")
end
end
f.put_character ('}')
f.close
end
end
save_value_as_json_string (obj: ANY; a_file: FILE; a_uuid: READABLE_STRING_GENERAL)
-- Pseudo json serialization.
-- Suggestion: use the Eiffel json library for standard.
local
conv: UTF_CONVERTER
s: STRING
l_is_first: BOOLEAN
do
if attached {WSF_STRING} obj as str then
s := conv.utf_32_string_to_utf_8_string_8 (str.value)
s.prune_all ('%R')
s.replace_substring_all ("%N", "\n")
s.replace_substring_all ("'", "\'")
a_file.put_character ('"')
a_file.put_string (s)
a_file.put_character ('"')
elseif attached {WSF_UPLOADED_FILE} obj as fup then
save_form_uploaded_file (a_uuid, fup)
a_file.put_character ('"')
a_file.put_string (conv.utf_32_string_to_utf_8_string_8 (fup.safe_filename))
a_file.put_character ('"')
elseif attached {WSF_TABLE} obj as tb then
a_file.put_character ('{')
l_is_first := True
across
tb as tb_ic
loop
if l_is_first then
l_is_first := False
else
a_file.put_character (',')
end
a_file.put_character ('"')
a_file.put_string (conv.utf_32_string_to_utf_8_string_8 (tb_ic.key))
a_file.put_character ('"')
a_file.put_character (':')
save_value_as_json_string (tb_ic.item, a_file, a_uuid)
end
a_file.put_character ('}')
end
end
wsf_theme: WSF_THEME
do
create {WSF_REQUEST_THEME} Result.make_with_request (request)
end
is_valid_yyyy_mm_dd_date (a_date: READABLE_STRING_GENERAL): BOOLEAN
local
i,j: INTEGER
y,m,d: INTEGER
str: STRING_32
s: STRING_32
do
create str.make_from_string_general (a_date)
str.left_adjust
str.right_adjust
Result := False
i := 1
-- YYYY-MM-DD
j := str.index_of ('-', i)
if j > i then
s := str.substring (i, j - 1)
if s.is_integer then
y := s.to_integer
i := j + 1
-- MM-DD
j := str.index_of ('-', i)
if j > i then
s := str.substring (i, j - 1)
if s.is_integer then
m := s.to_integer
if 1 <= m and m <= 12 then
i := j + 1
-- DD
s := str.substring (i, str.count)
if s.is_integer then
d := s.to_integer
if 1 <= d and d <= 31 then
-- This is just a basic validation.
Result := True
end
end
end
end
end
end
end
end
end