Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Introduction to Elm
<div id="root"></div>
<script>
var validator = new ValidatedForm($("#root"));
validator.render();
</script>
module ValidatedForm exposing (..)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Regex exposing (contains, regex)
main : Program Never Model Msg
main =
Html.beginnerProgram { model = model, view = view, update = update }
-- MODEL
type alias Model =
{ name : String
, phone : String
}
model : Model
model =
{ name = ""
, phone = ""
}
-- UPDATE
type Msg
= UpdateName String
| UpdatePhone String
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateName newName ->
{ model | name = newName }
UpdatePhone newPhone ->
{ model | phone = newPhone }
-- VIEW
hasNameValidationError : String -> Bool
hasNameValidationError nameEntry =
nameEntry == ""
hasPhoneValidationError : String -> Bool
hasPhoneValidationError phoneEntry =
let
phoneRegex =
"^(\\+\\d{1,2}\\s)?\\(?\\d{3}\\)?[\\s.-]{0,1}\\d{3}[\\s.-]{0,1}\\d{4}$"
in
not <| contains (regex phoneRegex) phoneEntry
nameErrorMessage : Bool -> String
nameErrorMessage hasNameError =
if hasNameError then
"Name must be supplied"
else
""
hasAnyError : Model -> Bool
hasAnyError model =
(hasNameValidationError model.name) || (hasPhoneValidationError model.phone)
phoneErrorMessage : Bool -> String
phoneErrorMessage hasPhoneError =
if hasPhoneError then
"Phone must match format 555-555-1234"
else
""
view : Model -> Html Msg
view model =
Html.form [ action "/path/to/api" ]
[ div [ class "form-group" ]
[ input
[ placeholder "Please enter something"
, onInput UpdateName
, value model.name
, name "name"
]
[]
, div [] [ model.name |> (hasNameValidationError >> nameErrorMessage) |> text ]
]
, div [ class "form-group" ]
[ input
[ placeholder "Please enter something"
, onInput UpdatePhone
, value model.phone
, name "tel"
]
[]
, div [] [ model.phone |> (hasPhoneValidationError >> phoneErrorMessage) |> text ]
]
, button [ disabled (hasAnyError model) ] [ text "Submit" ]
]
class ValidatedForm {
constructor(parentEl) {
this.parentEl = parentEl;
this.isValid = true;
}
render() {
const formEl = $(`
<form>
<div class="form-group">
<input type="text" name="name" placeholder="Name" />
<div class="form-group-validation"></div>
</div>
<div class="form-group">
<input type="phone" name="phone" placeholder="Telephone" />
<div class="form-group-validation"></div>
</div>
<button type="submit">Submit</button>
</form>
`);
this.parentEl.html(formEl);
this.formEl = formEl;
this.nameEl = formEl.find("[name='name']");
this.phoneEl = formEl.find("[name='phone']");
this._bind();
return this.parentEl;
}
_bind() {
this._bindInputHandlers();
this._bindFormSubmit();
}
_bindInputHandlers() {
this.nameEl.on("input", () => this._validate());
this.phoneEl.on("input", () => this._validate());
}
_validate() {
const isNameValid = this._validateNameField(this.nameEl.val());
const isPhoneValid = this._validatePhoneField(this.phoneEl.val());
this._renderNameValidationMessage(isNameValid);
this._renderPhoneValidationMessage(isPhoneValid);
const isAllValid = isNameValid && isPhoneValid;
this._disableSubmitButton(isAllValid);
return isAllValid;
}
_disableSubmitButton(isValid) {
this.parentEl.find("button").prop("disabled", !isValid);
}
_renderPhoneValidationMessage(isValid) {
this.phoneEl.toggleClass("has-error", !isValid);
this.phoneEl
.parent()
.find(".form-group-validation")
.text(isValid ? "" : "Phone number invalid");
}
_renderNameValidationMessage(isValid) {
this.nameEl.toggleClass("has-error", !isValid);
this.nameEl
.parent()
.find(".form-group-validation")
.text(isValid ? "" : "Name invalid");
}
_validateNameField(value) {
return value != null && value !== "";
}
_validatePhoneField(value) {
return (
value.match(
/^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]{0,1}\d{3}[\s.-]{0,1}\d{4}$/
) !== null
);
}
_bindFormSubmit() {
this.formEl.on("submit", e => {
if (!this.isValid) {
e.preventDefault();
e.stopPropagation();
}
});
}
}
type alias Model =
{ name : String
, phone : String
}
model : Model
model =
{ name = ""
, phone = ""
}
type Msg
= UpdateName String
| UpdatePhone String
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateName newName ->
{ model | name = newName }
UpdatePhone newPhone ->
{ model | phone = newPhone }
view : Model -> Html Msg
view model =
Html.form [ action "/path/to/api" ]
[ div [ class "form-group" ]
[ input
[ placeholder "Please enter something"
, onInput UpdateName
, value model.name
, name "name"
]
[]
, div [] [ model.name |> (hasNameValidationError >> nameErrorMessage) |> text ]
]
, div [ class "form-group" ]
[ input
[ placeholder "Please enter something"
, onInput UpdatePhone
, value model.phone
, name "tel"
]
[]
, div [] [ model.phone |> (hasPhoneValidationError >> phoneErrorMessage) |> text ]
]
, button [ disabled (hasAnyError model) ] [ text "Submit" ]
]
class ValidatedForm {
constructor(parentEl) {
this.parentEl = parentEl;
}
render() {
Elm.ValidatedForm.embed(this.parentEl)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment