Last active
March 3, 2017 19:43
-
-
Save cmontella/56fda335208ba82af8e327f95a0b9efc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Example 3 - Jurassic Park Terminal | |
## 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. | |
## 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. | |
```eve | |
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. | |
```eve | |
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 {{user.username}}!"] | |
[#div text: "Name: {{user.name}}"] | |
[#div text: "Department: {{user.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. | |
```eve | |
search @browser | |
form = [#form] | |
bind @browser | |
form += #div | |
form.sort := 0 | |
form.class := "form" | |
form.submission := [] | |
``` | |
Display the form name | |
```eve | |
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. | |
```eve | |
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 | |
```eve | |
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. | |
```eve | |
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 | |
```eve | |
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 | |
```eve | |
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 | |
```eve | |
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. | |
```eve | |
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 | |
```eve | |
search @browser | |
password = [#password] | |
bind @browser | |
password += #input | |
password.type := "password" | |
password.class := "password" | |
``` | |
Render custom button styles | |
```eve | |
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 | |
```css | |
{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} | |
``` | |
```css | |
.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: 690px; | |
background-size: 100% 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; | |
} | |
``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment