Implementing Material Design for the Web on My New Demo Website

· 9 min read

My most recent project has been creating a new Material Design 2.0 demo website, used for hosting any small coding projects I may have (e.g. for Computer Science lessons). The idea came about last Thursday, after I was given a Computer Science assignment to replicate the Luhn Algorithm. I was already keen to experiment with Material Design 2.0 (what with it being released only a few months ago), and I enjoy developing websites - so a natural step was to create a Material Design web app.Firstly, you may be wondering what on earth is the Luhn Algorithm? It is a checksum algorithm used for credit cards and the like - it checks that the card number entered is valid, using a rather simple process: it reverses the digits, then each digit in an odd indexed position (based on zero-indexing) is doubled. If this comes to more than 9 (i.e. more than one digit), it subtracts 9 (the same as adding the digits). Finally, if the sum of all of the digits (replacing those at odd-indexed positions with the new ones) modulo 10 is zero, the number is valid. This is algorithm is commonly used internationally.

The first part of the coding process involved working out how to implement the Material Design library on a GitHub Pages site (I'm using GitHub to host my demo site - see mgrove36/demo-code). Rather unhelpfully, all of the implementation examples on the Material Design website involve using the command:

npm i material-components-web

Which I can't do (as far as I know) on GitHub Pages. This meant I had to find an alternative implementation method. Eventually, I found a rather useful post on medium.com with what I wanted - instructions on how to implement Material Design on a small, static website. This allowed me to initiate the Material Design libraries, and to understand how a basic button works:

  1. Firstly, include the Material Design JavaScript and CSS libraries by including the following code in your <head> tag. I have also included the material icons font (yes, it's a font - more on this to come), as I will utilise the icons later, but it is optional:
    <!-- Material Design CSS library -->
    <link rel="stylesheet" href="https://unpkg.com/material-components-web/dist/material-components-web.min.css">
    <!-- Material Design JS library -->
    <script src="https://unpkg.com/material-components-web/dist/material-components-web.min.js"></script>
    <!-- OPTIONAL: Material Icons font -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    
  2. Then, I used the Material Design page for buttons to include the relevant HTML for a button. This was one of the easiest steps, as it just involved copying the code directly from the website and altering the content of the button. All that is involved is a <button> tag and the class mdc-button - for reference, MDC stands for Material Design Components; it is the Material Design library:
    <button class="mdc-button">
      Button
    </button>
    
  3. After doing some research into set-up methods for the button ripple animation (something that you've almost certainly already come across, as it is used all over the web), I concluded that it would be easiest to use the built-in mdc.autoInit() function - one that only needs to be run once on each page, and takes the content of the data-mdc-auto-init attribute for each HTML element and applies the relevant animations. For example, I used the following code for the buttons:
    <button class="mdc-button" mdc-data-auto-init="MDCRipple">
      Button
    </button>
    
  4. So, when using the mdc-data-auto-init attribute, ensure you include the following code in the JS for the page:
    mdc.autoInit();
    
  5. This should make all of the desired animations work perfectly.The coding got a lot easier from here on in, as I had a much better understanding of the library.I then decided to change the button styling to an outlined button, as opposed to the default one. Luckily, this involved only a small change in the code - adding an extra class to the <button> tag:
    <button class="mdc-button mdc-button--outlined" mdc-data-auto-init="MDCRipple">
      Button
    </button>
    

Finally, I had a Material Design button working. Next, I wanted a text input field, and chose the outlined style again on the text input field demo Material Design page. Therefore, I added the following code (copied from the text input field page at material.io, with one small edit):

<div>
    <div class="mdc-text-field mdc-text-field--outlined" data-mdc-auto-init="MDCTextField">
        <input type="text" id="tf-outlined" class="mdc-text-field__input">
        <label for="tf-outlined" class="mdc-floating-label">Your Name</label>
        <div class="mdc-notched-outline">
        <svg>
        <path class="mdc-notched-outline__path"/>
        </svg>
        </div>
        <div class="mdc-notched-outline__idle"></div>
    </div>
</div>

If you only want the default text field, use:

</div>
    <div class="mdc-text-field" data-mdc-auto-init="MDCTextField">
        <input type="text" id="my-text-field" class="mdc-text-field__input">
        <label class="mdc-floating-label" for="my-text-field">Hint text</label>
        <div class="mdc-line-ripple"></div>
    </div>
</div>

Rather importantly, I have put the code from the material.io website into an extra <div>. This is due to a bug which means the input field is wider than the viewport, rather annoyingly enabling horizontal scrolling. The fix also involves applying the following styling to the container div. I used the class mdc-text-field-container for it, but it can be whatever you want:

.mdc-text-field-container {
   overflow: hidden;
}
.mdc-text-field {
   margin-top: 5px;
}

The margin-top style is to ensure the text that moves to the top of the input field when it is selected is always visible, as otherwise it can be cut off by the container. Also notice that I used the mdc-data-auto-init attribute again, but this time to specify the text field animation. The final thing to take note of is the <label> just after the input field (<input>) - this is the text field helper text: the text that becomes visible as a hint when the text field is selected. It is also possible to make it persistent, by adding the class mdc-text-field-helper-text--persistent.

The next element to be added was a top app bar - the title bar you see at the top of Material Design apps. This was, again, rather simple, as it just involved copying from the material.io website. This top app bar has a menu button too, for activating a drawer (I'll cover them later). This icon is one of the Material Icons mentioned briefly earlier; material icons just need an element which has the class material-icons and the content of the name of the icon desired - so in this example, the name of the icon is menu, and the content of the element (in this case an <a> tag) is menu:

<header class="mdc-top-app-bar">
  <div class="mdc-top-app-bar__row">
    <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start">
      <a href="#" class="material-icons mdc-top-app-bar__navigation-icon">menu</a>
      <span class="mdc-top-app-bar__title">Title</span>
    </section>
  </div>
</header>

Note that the top app bar is initiated manually in JS though - not via autoInit():

const mdc_top_app_bar = new mdc.topAppBar.MDCTopAppBar.attachTo(document.querySelector('.mdc-top-app-bar'));
mdc_top_app_bar.setScrollTarget(document.getElementById('main-content'));

The first line initiates the top app bar, and the second specifies where the main content is. This means that the main content of the webpage needs to be in a container with the id main-content. I recommend that this is a <main> tag.

The drawer I decided upon (for navigation) was the modal drawer. This is one that is opened by clicking the menu button on the top app bar and closed by clicking the dimmed area to the right of it. This requires two things: the drawer and an empty div with the class mdc-scrim. This is used to dim the main content; the modal drawer doesn't work without it.With this drawer, the top app bar and main content go in a div together (using the class name mdc-drawer-app-content), so the code is:

<aside class="navbar-insert mdc-drawer mdc-drawer--modal">
    <div class="mdc-drawer__content">
        <div class="mdc-list">
            <a class="mdc-list-item" href="#">
                <span class="mdc-list-item__text">Navbar Item</span>
            </a>
        </div>
    </div>
</aside>

<div class="mdc-drawer-scrim"></div>

<div class="mdc-drawer-app-content">
    <header class="mdc-top-app-bar app-bar" id="app-bar">
        <div class="mdc-top-app-bar__row">
            <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start">
                <a href="javascript:void(0);" class="material-icons mdc-top-app-bar__navigation-icon">menu</a>
                <span class="mdc-top-app-bar__title">Page Title</span>
            </section>
        </div>
    </header>
    <main class="main-content" id="main-content">
        <div class="mdc-top-app-bar--fixed-adjust">
            <!-- main content -->
        </div>
    </main>
</div>

The JavaScript is as follows (the only additions are the initiation of the drawer and the code for the drawer to be toggled:

// initiates the drawer
const drawer = new mdc.drawer.MDCDrawer.attachTo(document.querySelector('.mdc-drawer'));

// initiates the top app bar
const mdc_top_app_bar = new mdc.topAppBar.MDCTopAppBar.attachTo(document.querySelector('.mdc-top-app-bar'));
mdc_top_app_bar.setScrollTarget(document.getElementById('main-content'));

// allows the drawer to be toggled
mdc_top_app_bar.listen('MDCTopAppBar:nav', () =&gt; {
    drawer.open = !drawer.open;
});

// only if you are using the data-mdc-auto-init attribute
mdc.autoInit();

So there we have it: a Material Design webpage. I also added a few extra tags in the <head>, in order to make it into a web app that can be added to the home screen on various devices:

<!-- add to homescreen for Chrome on Android -->
<meta name="mobile-web-app-capable" content="yes">
<link rel="icon" sizes="192x192" href="https://matthew-grove.ml/logo.png">

<!-- add to homescreen for Safari on iOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Luhn Algorithm | Matthew Grove">
<link rel="apple-touch-icon-precomposed" href="https://matthew-grove.ml/logo.png">

<!-- tile icon &amp; colour for Windows 8 -->
<meta name="msapplication-TileImage" content="https://matthew-grove.ml/logo.png">
<meta name="msapplication-TileColor" content="#d84315">

The penultimate step was to code the Luhn Algorithm itself, taking data from an input field, verifying the number when a "check" button is pressed (or when the enter key is pressed, no matter what is selected on the page) and displaying a verification message. I also wanted the verification message to disappear when the number's edited.

The code for the algorithm itself was rather simple, only taking around 15 minutes to write. It is as follows:

// define input string and remove non-numerical characters
var input = document.getElementsByTagName("input")[0].value.replace(/\D/g,'');;
// check string isn't empty
if (input) {
    // retrieve digits & reverse
    var digits = document.getElementsByTagName("input")[0].value.split("").reverse(), sum = 0;
    digits.forEach(function(currentDigit, index) {
        newDigit = Number(currentDigit);
        if (index % 2 == 1) {
            if ((newDigit *= 2) > 9) {
                newDigit -= 9;
            }
        }
        sum += newDigit;
    });

    // displays evaluation message
    $("#validation_message").html(((sum % 10) == 0) ? "Number is valid" : "Number is invalid");
}

Finally, I decided to dynamically insert the top app bar, drawer and "view in GitHub" link that I add to every page. This simply involved using the jQuery load() method and putting the subsequent JS in the function that runs on completion, to ensure that all of the required elements are in the DOM. The completed code is:

$(document).ready(function(){
    // include navbar
    $(".navbar-insert").load("/page-inserts/navbar.html", function(){
        // initiate MDC drawer
        const drawer = new mdc.drawer.MDCDrawer.attachTo(document.querySelector('.mdc-drawer'));

        // include top app bar
        $(".mdc-top-app-bar").load("/page-inserts/top-app-bar.html", function(){
            // initiate MDC top app bar
            const mdc_top_app_bar = new mdc.topAppBar.MDCTopAppBar.attachTo(document.querySelector('.mdc-top-app-bar'));
            mdc_top_app_bar.setScrollTarget(document.getElementById('main-content'));
            mdc_top_app_bar.listen('MDCTopAppBar:nav', () => {
                drawer.open = !drawer.open;
            });

            // include source code link
            $(".source-code-link").load("/page-inserts/source-code-link.html");

            // initiate MDC items
            mdc.autoInit();

            // get current URL with no forward slash at the end and no domain
            var drawer_item_link_query_selector = ".mdc-list-item[href='" + window.location.pathname;
            if (drawer_item_link_query_selector.substring(drawer_item_link_query_selector.length - 1) == "/") {
                drawer_item_link_query_selector = drawer_item_link_query_selector.substring(0,drawer_item_link_query_selector.length - 1);
            }

            // give 'selected' styling to correct item on navbar
            $(drawer_item_link_query_selector + "']").addClass("mdc-list-item--activated");
            $(drawer_item_link_query_selector + "']").attr("aria-selected", "true");
            $(drawer_item_link_query_selector + "/']").addClass("mdc-list-item--activated");
            $(drawer_item_link_query_selector + "/']").attr("aria-selected", "true");
        });
    });
});

I hope this post has helped you to understand the Material Design library better; please do leave a comment or contact me if you need any help.

Please check out my GitHub, @mgrove36, and GitLab, @mgrove36.