Example 3 - Jurassic Park
Corey Montella - 01 Mar 2017
What is this?
This app demonstrates the use of a custom form component in a basic log in application. The app contains three views: a login form, a registration form, and a user profile form that displays the information entered during registration. These forms are built using a custom form component, which is defined at the end of the program. The form component allows for the concise definition of forms by defining common behavior like form submission and resetting.
You can play with this example in your browser here.
Application Set Up
The app contains the current page, as well as the current user. Initially, though, there is no user, so we just need to specify the current page.
bind @browser
[#div #app class: "app-wrapper" page: "login" children:
[#div sort: 0 text: "Jurassic Park System Security Interface"]]
Pages
Log In Form
The log in form contains two input boxes, one for the username and another for a password. We must explicitly sort the fields (using a sort
attribute) to display them in a specific order.
search @browser
app = [#app page: "login"]
bind @browser
app.children +=
[#div sort: 1 children:
[#form name: "Login" sections:
[#section fields:
[#field sort: 2 type: "password" field: "password"]
[#field sort: 1 type: "input" field: "username"]]]
[#button #signup text: "Sign Up" ]]
A successful login is one where the username and password entered in the login form match some user/password combination stored in the system. For simplicity, passwords are stored as plain text, so we just need to search for a #user
with a matching username and password. If one is found, we set it as the user attribute in the #app
record.
search @browser @session
[#form name: "Login" submission: [username password]]
user = [#user username password]
app = [#app]
commit @browser
app.page := "profile"
app.user := user
If the user enters a login that does not match a user, then display a message indicating that the login failed.
search @browser @session
form = [#form name: "Login" submission: [username password]]
form-message = [#form-message form]
not([#user username password])
commit @browser
form-message.text := "** Login Failed **"
Clicking the sign up button changes the page to the sign up page
search @browser @event
[#click element: [#signup]]
app = [#app]
commit @browser
app.page := "signup"
Sign Up Form
The user registration page requests the name, department, a username and password.
search @browser
app = [#app page: "signup"]
bind @browser
app.children +=
[#div children:
[#form name: "Sign Up" options: [reset: true] sections:
[#section name: "User Info" fields:
[#field sort: 1 type: "input" field: "full-name"]
[#field sort: 2 type: "input" field: "department"]]
[#section name: "Account Info" fields:
[#field sort: 1 type: "input" field: "username"]
[#field sort: 2 type: "password" field: "password"]
[#field sort: 3 type: "password" field: "confirm-password"]]]
[#button #login text: "Log In" ]]
We need to create a #user
from the submission of the registration form. This will only work if every field has an entry, and the two password fields match.
search @browser
[#form name: "Sign Up" submission: [username password confirm-password full-name department]]
// The password and the confirmation must match
password = confirm-password
app = [#app]
commit @browser
app.page := "login"
commit
[#user username password name: full-name department]
Clicking the login button changes the page back to the login screen
search @browser @event
[#click element: [#login]]
app = [#app]
commit @browser
app.page := "login"
Profile Page
The profile page displays information relating to the current user profile. It is accessed after a successful submission of the login form, which creates a user attribute in the #app.
search @browser @session
app = [#app page: "profile" user]
bind @browser
app.children +=
[#div class: "profile" children:
[#div text: "Welcome !"]
[#div text: "Name: "]
[#div text: "Department: "]
[#button #logout text: "Log Out"]]
Clicking logout returns to the login page, and removes the user from #app
.
search @browser @event
[#click element: [#logout]]
app = [#app]
commit @browser
app.user := none
app.page := "login"
An easter egg
We can specify custom behavior by special casing search conditions and adding new side effects. In this block, we hijack the login process when the username is “dnedry”. Instead of displaying the typical “login failed” message, we give the user a surprise.
search @browser @session
app = [#app]
[#form name: "Login" submission: [username password]]
username = "dnedry"
not([#user username password])
commit @browser
app.children := none
app.class -= "app-wrapper"
app.class += "uh-uh-uh"
Clicking anywhere returns to the login screen
search @browser @session @event
[#click]
app = [#app class: "uh-uh-uh"]
commit @browser
app.class += "app-wrapper"
app.class -= "uh-uh-uh"
app.children += [#div sort: 0 text: "Jurassic Park System Security Interface"]
app.page := "login"
A Custom Form Element
Forms have a title and one or more sections. Each section has an optional name, and contains one or more fields. Each field additionally has the input type of that field (input, radio button, drop down list, etc.).
A form starts as a #form
record.
search @browser
form = [#form]
bind @browser
form += #div
form.sort := 0
form.class := "form"
form.submission := []
Display the form name
search @browser
form = [#form]
bind @browser
form.children += [#div children:
[#h1 class: "form-name" sort: 0 text: form.name]
[#div #form-message form sort: 1 class: "form-message"]]
Display each section. To properly display sections, we need to add them to the children of the form.
search @browser
form = [#form sections]
bind @browser
form.children += [#div form section: sections class: "form-section" sort: 1]
sections.form := form
If the section has a name, display it
search @browser
section-display = [#div section]
bind @browser
section-display.children += [#h2 class: "section-name" text: section.name, sort: 0]
Display the fields in each section. As we did with sections, to display fields we need to move them over to the children of the section display.
search @browser
section-display = [#div section]
field = section.fields
bind @browser
section-display.children +=
[#div field sort: field.sort form: section.form sort: 1 children:
[tag: field.type, placeholder: field.field, class: field.type]]
Display a submit button at the end of the form
search @browser
form = [#form]
bind @browser
form.children += [#button #submit form sort: 100 text: "Submit"]
Forms can have an optional reset button, which clears the fields in the form
search @browser
form = [#form options: [reset: true]]
bind @browser
form.children += [#button #reset form sort: 101 text: "Reset"]
Clicking the reset button clears each field in the form
search @event @browser
[#click element: [#reset]]
field-container = [#div field form]
commit @browser
field-container.children.value := none
Save Input to Records
Form values are saved as a #submission
when the submit button is clicked. This submission has a lifetime equal to that of the #click
, so a submission must be committed to a record by the user. This allows the user to implement custom handling logic.
One thing this form component does not handle is form validation. In a future example, we will demonstrate how certain fields can be required, while others are optional. This form will submit any fields that are filled, while omitting any that are not. If a form is handled expecting fields that aren’t submitted, then the submission will simply be ignored.
search @browser @event @session
click = [#click element: [#submit form]]
form = [#form submission]
field-container = [#div field form]
value = field-container.children.value
key = field.field
[#time timestamp: time]
bind @browser
// When used in a bind or commit. lookup[] creates a record with the give attribute and value. We use it here to create a record with the attribute as the field name.
//For example, a login form with "username" and "password" fields could be accessed as [#form name: "Login" submission: [username password]]
lookup[record: submission, attribute: key, value]
field-container.children.value := none
Custom Input Types
Render password fields
search @browser
password = [#password]
bind @browser
password += #input
password.type := "password"
password.class := "password"
Render custom button styles
search @browser
button = [#button]
bind @browser
button.class += "button"
Appendix
Test Data
commit
[#user username: "jhammond" name: "John Hammond" department: "Executive" password: "password"]
[#user username: "dnedry" name: "Dennis Nedry" department: "Engineering" password: "Mr. Goodbytes"]
[#user username: "hwu" name: "Henry Wu" department: "Genetics" password: "slartibartfast"]
Styles
{there is currently a bug that causes the first CSS block in an Eve program to be disregarded, so for a good time, leave this here}
.application-container {
background-color: #000;
color: green;
font-family: monospace;
}
@media (min-width: 1800px) {
.app-wrapper {
background-image: url(http://i.imgur.com/BBPkd29.gif);
background-repeat: no-repeat;
width: 800;
height: 658px;
background-size: 100%;
padding: 180px;
padding-top: 130px;
}
}
.profile {
border: 1px solid green;
padding: 10px;
font-size:18px;
border-radius: 5px;
}
.profile div {
padding: 10px;
}
.form-section {
border: 1px solid green;
border-radius: 5px;
padding: 10px;
margin: 10px;
}
.form {
border: 1px solid green;
border-radius: 5px;
padding: 10px;
margin: 10px;
color: green;
}
.input {
background-color: #000;
border-radius: 5px;
border: 1px solid green;
padding: 5px;
margin: 5px;
font-family: monospace;
color: green;
outline: none;
}
.password {
background-color: #000;
border-radius: 5px;
border: 1px solid green;
padding: 5px;
margin: 5px;
font-family: monospace;
outline: none;
color: green;
}
.button {
background-color: #000;
color: green;
border-radius: 5px;
border: 1px solid green;
padding: 5px;
margin: 5px;
cursor: pointer;
}
.form-message {
color: green;
}
.form-name {
margin: 0px;
}
.section-name {
margin: 0px;
}
.uh-uh-uh {
width: 320px;
height: 520px;
background-image: url(http://i.imgur.com/yz53s4N.gif);
background-color: #FFF;
}