playframework - Styling the login

Posted on by Mandla Mbuli

Bulma

I want to use Bulma for styling for now. This is simply because I’m lazy to do all the CSS. Why Bulma? - I didn’t put a lot of thought into this, I just needed something that can do the CSS for me.

Adding bulma classes

From the Bulma docs, I wanted to reproduce this form:

<div class="field">
  <p class="control has-icons-left has-icons-right">
    <input class="input" type="email" placeholder="Email">
    <span class="icon is-small is-left">
      <i class="fas fa-envelope"></i>
    </span>
    <span class="icon is-small is-right">
      <i class="fas fa-check"></i>
    </span>
  </p>
</div>
<div class="field">
  <p class="control has-icons-left">
    <input class="input" type="password" placeholder="Password">
    <span class="icon is-small is-left">
      <i class="fas fa-lock"></i>
    </span>
  </p>
</div>
<div class="field">
  <p class="control">
    <button class="button is-success">
      Login
    </button>
  </p>
</div>

This needed a few things:

  • Add Bulma
  • Add fontawesome icons
  • Add the required wrappers and classed to the playframework form fields

The first 2 items were each, I just downloaded the CSS files for Bulma and fontawesome and just added them to the public/stylesheets directory.

 exa -RTL 2 public
public
├── images
  ├── favicon.png
  └── website-bg.jpg
├── javascripts
  └── main.js
└── stylesheets
   ├── fontawesome
   ├── bulma.min.css
   ├── main.css
   └── website

I then link these in the website base template:

<!DOCTYPE html>
<html lang="en">
  <head>
...
    <link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.versioned("stylesheets/bulma.min.css")"/>
    <link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.versioned("stylesheets/fontawesome/css/fontawesome.css")"/>
    <link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.versioned("stylesheets/fontawesome/css/brands.css")"/>
    <link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.versioned("stylesheets/fontawesome/css/solid.css")"/>
...
    <style>
...

Now on to the last thing.

Custom field constructor

I followed the playframework docs on custom field constructors for this.

Firstly, needed to add an template (authentication/templates/fields.scala.html) for the fields

@(elements: helper.FieldElements)

<div class="field @if(elements.hasErrors) {error}">
    <label for="@elements.id" class="label">@elements.label</label>
    <div class="control">
      @elements.input
    </div>
    <span class="errors">@elements.errors.mkString(", ")</span>
    <span class="help">@elements.infos.mkString(", ")</span>
</div>

This just adds the label.class (a label with a class=“label”) on the label tag and wraps the input with a div.control (a div with class=“control”, should find where I got this syntax from). It seems playframework doesn’t allow changing the input here to add the rest of the Bulma classes.

I then added an object that

package authentication

object Fields {
  import views.html.helper.FieldConstructor

  implicit val myFields: FieldConstructor = FieldConstructor(
    authentication.templates.html.fields.f
  )
}

I also needed to change the folder that contains the authentication views from views to templates because metals kept warning that html.helper.FieldConstructor is not part of “authentication.views.html”. This is probably because I changed the layout playframework expects.

I then add an import of the template to the authentication/templates/loginForm.: @import authentication.Fields._

and the file looks like:

@import authentication.Fields._

@(form: Form[authentication.models.User], postUrl: Call)(implicit request: MessagesRequestHeader)


@request.flash.data.map{ case (name, value) =>
  <div>@name: @value</div>
}

@* Global errors are not tied to any particular form field *@
@if(form.hasGlobalErrors) {
  @form.globalErrors.map { (error: FormError) =>
    <div>
      Error: @error.key: @error.message
    </div>
  }
}

@helper.form(postUrl, Symbol("id") -> "user-login-form") {
  @helper.CSRF.formField

  @helper.input(
    form("username"),
    Symbol("_label") -> "Username",
    Symbol("placeholder") -> "username",
    Symbol("id") -> "username",
    Symbol("size") -> 60
  ) { (id, name, value, args) =>
    <p class="control has-icons-left has-icons-right">
      <input class="input" type="text" name="@name" id="@id" @toHtmlArgs(args)>
      <span class="icon is-small is-left">
        <i class="fas fa-user"></i>
      </span>
      <span class="icon is-small is-right">
        <i class="fas fa-check"></i>
      </span>
    </p>
  }

  @helper.input(
    form("password"),
    Symbol("_label") -> "Password",
    Symbol("placeholder") -> "password",
    Symbol("id") -> "password",
    Symbol("size") -> 60
  ) { (id, name, value, args) =>
    <p class="control has-icons-left">
      <input class="input" type="password" name="@name" id="@id" @toHtmlArgs(args)>
      <span class="icon is-small is-left">
        <i class="fas fa-lock"></i>
      </span>
    </p>
  }

<div class="field">
  <p class="control">
    <button class="button is-success">
      Login
    </button>
  </p>
</div>

}

Here, instead of using the playframework helpers @helper.inputText and @helper.inputPassword, I use the @helper.input and set the input types myself. This allows me to add the rest of the classes I need for Bulma.

This now gives me a loginForm template which I can now use in the website login.scala.html as follows:

@import authentication.templates.html._

@(form: Form[authentication.models.User], postUrl: Call)(implicit request: MessagesRequestHeader)

@base("- login") {
  <div id="content">
    <div id="user-login-form">
      @loginForm(form, postUrl)(request)
    </div>
  </div>
}

and now I have a styled login page. Need to polish but looks good enough so far.

Next

Reading file for users


Mandla