Customizing the Login Screen

John Bissonette edited this page Mar 17, 2016 · 2 revisions
Clone this wiki locally

Customizing Serenity's Login Page

Serenity has a default login page that is very basic. In order to customize the look of the page, it is necessary to alter some of the .less files in MyProject.Web\Content\site. Additionally, some changes might need to be made to the AccountLogin.cshtml page as well.

To begin, the following file needs to be changed in order to customize the appearance of the default login panel:

  • ..\MyProject.Web\Content\Site\site.membership.less

Additionally, you may need to make changes to these files as well in order to further customize the login panel:

  • ..\MyProject.Web\Content\site\site.mixins.less
  • ..\MyProject.Web\Modules\Membership\Account\AccountLogin.cshtml
  • ..\MyProject.Web\Modules\Texts.cs

If you would like to use any external JavaScript libraries in your new login page, add them to the MyProject.Web\Scripts folder, make sure to include them in your project with Visual Studio (if necessary using VCS). Add any custom stylesheets to MyProject.Web\Content\site folder. I will show you how to add custom stylesheets directly to the <head> tag and also how to include custom .less files to your project and Serenity will build them and add the generated CSS to your site.css file.

End Result

Here is the end result of the customizations made to the login page. Next, I will step through the changes necessary and describe what is happening and why they were made. Although this is a static image, keep in mind that the background is actually an animated slideshow, thanks to the awesome Vegas library by Jay Salvat.

Customized Login Page

Starting Out

It is a good idea to have a base idea of what you would like the end result of the login page to look like. I have decided that I wanted to use a fullscreen animated slideshow background using the aforementioned Vegas.js library by Jay Salvat. Additionally, I wanted the actual login panel to have a semi-transparent background and a customized header that included my organization's logo. So to begin, after conceptualizing the end result, we can begin by first downloading the external Vegas.js library from the Vegas homepage. It is possible to use npm (npm i vegas) or bower (bower install vegas) to a directory of your choice and copying the vegas folder from either ..\node_modules or ..\bower_components directories, if so chosen. Make sure to include the library in the project if using VCS in Visual Studio (it is highly recommended to use some kind of VCS for your application's development). Once that is complete, open up the AccountLogin.cshtml file. This is where we will begin making our customizations.

The CSHTML

The cshtml file contains a few things of note. At the top of the file, there is a Razor helper that defines the Title, PageId, and Layout. These do not need to be changed, unless you would like to change the page title that appears in a browser tab or use a different layout than the default _LayoutNoNavigation that Serenity provides. Following this is a <script> tag that contains the login panel widget's template information. Next, there are two more Razor helpers that deal with skipped migrations and new account activation. Following that are two <div> tags. They are:

<div class="page-content">
    <div id="LoginPanel">

    </div>
</div>

The <div id="LoginPanel"> container is used to inject the LoginPanel widget. The page-content class is unused for styling, but acts as a container for the widget's template.

Finally, at the end of the page there is another <script> tag, and this is of type="text/javascript". The previous script that contained the template information was of type="text/template". The difference here is that the template is used by Serenity's Script class to create the LoginPanel widget. This widget is injected and instantiated on the <div> with the id of "LoginPanel" by the JavaScript at the end of the page, like so:

<script type="text/javascript">
jQuery(function() {
    new MyProject.Membership.LoginPanel($('#LoginPanel')).init();

// ...
});
</script>

The first part of the customization is adding the Vegas library to the page. Right after the first Razor helper that defines the page title and layout, add a Razor section that will add the necessary files for the Vegas library to execute:

@{
    ViewBag.Title = "Login";
    ViewBag.PageId = "Login";
    Layout = MVC.Views.Shared._LayoutNoNavigation;
}

@section Head
{
    <link rel="stylsheet" href="~/Scripts/vegas.2.2.0/vegas.min.css />
    <script src="~/Scripts/vegas.2.2.0/vegas.min.js".</script>
}

This helper will include these two files in the <head> tag of your page when it is constructed. Next, initialize Vegas in the end JavaScript, right after the LoginPanel is instantiated:

<script type="text/javascript">
jQuery(function() {
    new MyProject.Membership.LoginPanel($('#LoginPanel')).init();

@if (ViewBag.Activated != null)
{ ... }

    $('body').vegas({
        // Lookup Vegas options at vegas.jaysalvat.com
        // Some are shown below
        delay: 5000,
        timer: false,
        overlay: Q.resolveUrl("~/Scripts/vegas.2.2.0/overlays/01.png"),
        cover: true,
        shuffle: true,
        transition: 'fade',
        animation: 'kenburns',
        slides: [
            { src: Q.resolveUrl("~/Content/site/images/image1.jpg") }
            { src: Q.resolveUrl("~/Content/site/images/image2.jpg") }
            { src: Q.resolveUrl("~/Content/site/images/image3.jpg") }
            { src: Q.resolveUrl("~/Content/site/images/image4.jpg") }
            { src: Q.resolveUrl("~/Content/site/images/image5.jpg") }
        ]
    });
});
</script>

I have added my images to the MyProject\Content\site\images folder, you can place them anywhere you'd like, but be sure to reference them properly. Q.resolveUrl() is used to resolve the path to the images, and to remove the tilde ~ symbol from the image path; otherwise, there would be a 404 error in the console. Now, when running the application, the login screen will show your slideshow in the background. Success! Now, we need to adjust the actual Login Panel's styling to make it look better and fit well within the page.

LoginPanel Widget

The Login Panel is a derived class from the PropertPanel<TEntity> abstract class, itself derived from the PropertyPanel<TEntity, TOptions> abstract class, which implements the TemplatedPanel<Toptions> abstract class, whom finally implements the base abstract class TemplatedWidget<TOptions> abstract class. To learn more about TemplatedWidgets, reference the following section of the Serenity Developer Guide: Templated Widget Guide.

The default template is below:

<script id="Template_Membership_LoginPanel" type="text/template">
<div>
    <h3>@Texts.Forms.Membership.Login.FormTitle</h3>
    <form id="~_Form" action="">
        <div class="s-Form">
            <div class="fieldset ui-widget ui-widget-content ui-corner-all">
                <div id="~_PropertyGrid"></div>
                <div class="clear"></div>
            </div>
            <div class="buttons">
                <button id="~_LoginButton" type="submit" class="btn btn-primary">
                    @Texts.Forms.Membership.Login.SignInButton
                </button>
                <a href="@Url.Content("~/Account/ForgotPassword")">@Texts.Forms.Membership.Login.ForgotPassword</a>
                <a class="clear" href="@Url.Content("~/Account/SignUp")">@Texts.Forms.Membership.Login.SignUpButton</a>
            </div>
        </div>
    </form>
</div>
</script>

The Razor helpers for Texts refers to the ..\MyProject.Web\Modules\Texts.cs class, specifically the nested class Login strings. It is here that we can add a new string to be used in our Login Panel, a welcome message.

// ...
public static class Forms
{
    public static class Membership
    {
        public static class Login
        {
            // ...
            public static LocalText WelcomeMessage = "Sign in to start your session";
            // ...
        }
    }
}

Now we can add our new welcome message to the template using Razor. Next step is to style our LoginPanel's title, which defaults to "Welcome to SERENE (Serenity Application Template)". We want to add our organization's logo, and style the title of our application as well. To do this, download an SVG of the logo and place it into the images folder. Next, alter the MyProject.Forms.Membership.FormTitle property to change the LoginPanel's title. Once this is complete, open up site.membership.less. In order to style the FormTitle text and adjust the base dimensions of the LoginPanel, make the desired changes like below:

@import "site.mixins.less";

.s-LoginPanel {
    padding: 20px;
    width: 570px;
    margin: 7% auto;
    background: rgba(0,0,0,0.5);
    .form-style(85px, 300px);

    h3 {
        font-size: 35px;
        font-weight: 300;
        text-align: left;
        margin-bottom: 10px;
        color: #fff;
    }

    .welcome {
        text-align: left;
        color: #fff;
        font-size: 12px;
        padding-left: 54px;
    }

    // ...
}

This adjusts the panel, and also makes adjustments to the editor styles, which uses the .form-styles(@l, @e) mixin. We have also added a "welcome" class, used for the welcome message provided earlier. In this example, I have also made adjustments to the inputs, the field class, and the caption class. The final look of the less file is:

@import "site.mixins.less";

.s-LoginPanel {
    padding: 20px;
    width: 570px;
    margin: 7% auto;
    background: rgba(0,0,0,0.5);
    .form-styles(85px, 300px);

    h3 {
        font-size: 35px;
        font-weight: 300;
        text-align: left;
        margin-bottom: 10px;
        color: #fff;
    }

    .welcome {
        text-align: left;
        color: #fff;
        font-size: 12px;
        padding-left: 54px;
    }

    .buttons {
        padding: 8px 0 0 158px;

        .btn-roc {
            background-color: #00467f;
            color: #ffdd00;

            &:hover {
                color: #f8971a;
            }
        }

        .links {
            float: right;
            margin-right: 45px;
            padding-right: 25px;

            a {
                color: #ffdd00;
            }
        }
    }

    .s-Form .field {
        padding-top: 0px;
        padding-bottom: 0px;
        padding-left: 158px;
        padding-right: 0px;
        position: relative;
    }

    .s-Form input {
        margin-top: 10px;
        margin-bottom: 20px;
        width: 300px;
        display: block;
        border: none;
        padding: 10px 0;
        border-bottom: solid 1px #ffdd00;
        -webkit-transition: all 0.5s cubic-bezier(0.64, 0.09, 0.08, 1);
          transition: all 0.5s cubic-bezier(0.64, 0.09, 0.08, 1);
        background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0) 96%, #ffdd00 4%);
        background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 96%, #ffdd00 4%);
        background-position: -300px 0px;
        background-size: 300px 100%;
        background-repeat: no-repeat;
        color: #ff8629;

        &:focus, :valid {
            box-shadow: none;
            outline: none;
            background-position: 0 0;
        }
    }

    .s-Form .caption {
        padding: 6px 10px;
        color: #d7d9da;
        position: absolute;
        top: 20px;
        left: 150px;
        font-size: 16px;
        transition: 0.5s ease all;
        pointer-events: none;
    }

    .s-Form .dirty {
        top: -10px;
        left: 140px;
        font-size: 12px;
        color: #ffdd00;
    }    
}

.s-ChangePasswordPanel {
    width: 550px;
    .form-styles(150px, 350px);
    .buttons { text-align: right; padding: 8px 48px; }
}

.s-ForgotPasswordPanel {
    border: 2px solid #e7e7e7;
    padding: 40px;
    border-radius: 8px;
    width: 650px;
    margin: 60px auto auto auto;
    .form-styles(150px, 350px);
    .buttons { text-align: right; padding: 8px 62px; }
    p { padding: 8px; margin: 24px 0; }
}

.s-ResetPasswordPanel {
    border: 2px solid #e7e7e7;
    padding: 40px;
    border-radius: 8px;
    width: 650px;
    margin: 60px auto auto auto;
    .form-styles(150px, 350px);
    .buttons { text-align: right; padding: 8px 62px; }
}

The key things to see in this file are the changes made to the .buttons class and its nested classes, .field nested class, input element, .caption and .dirty nested styles. These are used to create the Material Design style inputs. In order to make the labels float over the input and then animate their transitions when the input is clicked, the .dirty class is used. We use a small JavaScript snippet to make this work. Add it after Vegas is initialized on the body element:

<script type="text/javascript">
jQuery(function () {

    // omitted previous changes for brevity...

    $('input').focus(function() {
        $me = $(this);
        $myLabel = $('label[for="' + $me.attr('id') + '"]');

        $myLabel.addClass('dirty');
    });

    $('input').blur(function () {
        $me = $(this);
        $myLabel = $('label[for="' + $me.attr('id') + '"]');

        if (!($me.val()))
            $myLabel.removeClass('dirty');
    });
});
</script>

What this bit of jQuery does is first add the class .dirty to any input element that is brought into focus (clicking on it). The dirty class defines the end style of the element, while the .caption class defines the transition: transition: 0.5s ease all;. When the element is brought out of focus using jQuery's blur() function, the script checks to see if the input has anything typed in. If this check returns false, then the .dirty class is removed and the label returns to its original position. This prevents the label from returning and floating on top of any typed input the user has entered into the input element.

Finish the Template

We will finish the template by adding the welcome message, and adjusting the text of the title a bit. To make one word of the title bold while the second word is not, edit the Texts.Forms.Membership.Login.FormTitle property like so:

// ...

public static class Login
{
    public static LocalText FormTitle = "My<b>Project</b>";
}

It is necessary to make a small change to the template so that the title will appear like MyProject instead of My<b>Project</b>. Alter the Razor helper in the template like this:

// Logo inlined with the h3 element
<script id="Template_Membership_LoginPanel" type="text/template">
    <div>
        <h3>
            <img src="/Path/to/your/logo.svg" height="44" // line height in pixels
            @Html.Raw(@Texts.Forms.Membership.Login.FormTitle)
        </h3>
        // ...
    </div>
</script>

Without using the @Html.Raw Razor helper, the HTML <b> tags would be parsed as a literal part of the string. The final look of our template is:

<script id="Template_Membership_LoginPanel" type="text/template">
    <div>
        <h3>
            <img src="/Content/site/images/organization_logo.svg" height="44" /> 
            @Html.Raw(@Texts.Forms.Membership.Login.FormTitle)
        </h3>
        <p class="welcome">@Texts.Forms.Membership.Login.WelcomeMessage</p>
        <form id="~_Form" action="">
            <div class="s-Form">
                <div class="fieldset ui-widget ui-widget-content ui-corner-all">
                    <div id="~_PropertyGrid"></div>
                    <div class="clear"></div>
                </div>
                <div class="buttons">
                    <button id="~_LoginButton" type="submit" class="btn btn-roc btn-flat">
                        @Texts.Forms.Membership.Login.SignInButton
                    </button>
                    <div class="links">
                        <a href="@Url.Content("~/Account/ForgotPassword")">@Texts.Forms.Membership.Login.ForgotPassword</a>
                    </div>
                </div>
            </div>
        </form>
    </div>
</script>

And voila! We now have a cool, customized login screen for our application. In the future, I will post the changes to the forgot password, change password, and reset password panels to match the look of the example login panel.