Make a Flexbox Homepage

Could 2017 be the year of flex boxes? For years, web designers everywhere have been keeping a keen eye on the ever-evolving specification for flex boxes, slowly salivating at the thought that soon — Soon™ — difficulties around laying out, aligning and distributing blocks on a page could be a thing of the past.

For the uninitiated, creating a flexible layout entails the developer effectively setting rough (ie often non-exact or dynamic) values for elements within a container and then having the browser calculate how they should render. Alternatively, you can set very specific dimensions for your elements and, instead, use the flexible layout to determine how much whitespace to show around the elements.

For years, a working draft implementation has been supported by many browsers, if the developer prefixed the CSS properties (eg ms- for Internet Explorer, -webkit for Chrome, etc). In July 2013, Chrome supported it without the prefix, with Firefox following in March 2014. Safari and Edge joined in 2015. So why aren't we using it already? Well, sadly, it's not really supported by Internet Explorer.

There is partial support for it in IE10 and above, but with caveats: you must use the -ms prefix, you must use older syntax, and there are known bugs (that likely won't be fixed). In all, it is a mess. As you'll know, we've only recently started to drop support for IE8 — it'll likely be a while before we stop supporting IE9.

I think flex boxes can have some useful applications to an ecommerce site. There's been a move, designwise, towards homepages that have a 'magazine' look: where you have a lot of attention-grabbing content that offers a lot of options to kick off the shopper's visit. After all, by default, we offer a carousel in our templates, with the idea being that you show a variety of categories, products, offers, etc, to the customer in order to showcase your wares. Achieving a similar thing without a flex box is obviously doable, but requires either a lot of code to get right, or you end up with a somewhat stale, unscalable layout.

Basic Flex Box

To get an idea of how it works on a basic level, let's take a look at an example.

Before we begin, set up a new module in your customizations directory. I'm calling mine OverrideHome. I've added folders for templates and Sass. In these folders I've put an exact copy of home.tpl (renamed to override_home.tpl), and a blank Sass file called _override-home.scss).

Then, in my ns.package.json file, I've got the following:

{
  "gulp": {
    "templates": [
      "Templates/*"
    ],
    "sass": [
      "Sass/*.scss"
    ]
  },
  "overrides": {
    "suitecommerce/Home@2.1.0/Templates/home.tpl": "Templates/override_home.tpl"
  }
}

If you do a gulp deploy to get this up to your site and then load your homepage, you'll note that nothing's changed. Good — let's move things locally and start developing.

Run gulp local. After that, replace the contents of override_home.tpl with the following:

<div class= flex-home">
    <div class= flex-home-item-regular">1</div>
    <div class= flex-home-item-narrow">2</div>
    <div class= flex-home-item-regular">3</div>
    <div class= flex-home-item-regular">4</div>
    <div class= flex-home-item-regular">5</div>
    <div class= flex-home-item-wide">6</div>
    <div class= flex-home-item-narrow">7</div>
    <div class= flex-home-item-regular">8</div>
    <div class= flex-home-item-wide">9</div>
    <div class= flex-home-item-regular">10</div>
    <div class= flex-home-item-regular">11</div>
    <div class= flex-home-item-wide">12</div>
    <div class= flex-home-item-narrow">13</div>
</div>

As you can see, it's just a container and then a bunch of divs, each with one of three classes. I've numbered them to make recognising them on the frontend a little easier. Now let's style them.

In _override-home.scss, put the following:

.flex-home {
  display: -webkit-box;
  display: -moz-box;
  display: -ms-flexbox;
  display: -webkit-flex;
  display: flex;

  flex-flow: row wrap;
  justify-content: space-around;

  font-weight: bold;
  font-size: 100px;
  text-align: center;
}

.flex-home-item {
  color: white;
  margin: 3px;
  height: 30vw;
}

.flex-home-item-regular {
  @extend .flex-home-item;
  background: indianred;
  flex-grow: 1;
  width: 30vw;
  height: 30vw;
  min-width: 300px;
}

.flex-home-item-wide {
  @extend .flex-home-item;
  background: dodgerblue;
  flex-grow: 3;
  width: 60vw;
  min-width: 400px;
}

.flex-home-item-narrow {
  @extend .flex-home-item;
  background: darkseagreen;
  flex-grow: 1;
  width: 15vw;
  min-width: 200px;
}

If you reload the homepage on your local site, you should see large, colorful blocks with numbers in them. They should fit right across the screen and — this is definitely worth trying — when you adjust the viewport size, they change size and shift around as necessary.

What's interesting about this is that even though we've set widths on all of the blocks, ones with the same class are actually different widths, depending on the context of the other ones. Interesting, huh?

Let's take a look at some of the properties that we've set:

  • display: flex — along with the browser-specific versions of this property, this determines that the container is a flex container. It essentially says that everything contained within will be allowed to be made flexible.
  • flex-flow: row wrapthis is a shorthand version of two properties that determine the direction and wrapping of the elements in the container. We've told it to set the elements on a row, but you can also set them as a column, and you can also tell it to be presented in reverse order. As for the second value, we're stating what should happen when elements don't fit well: we've told them to wrap onto the next line, but you can prevent this or, again, set it in reverse.
  • justify-content: space-around — given the size of our items, this isn't so important in this particular case but it determines what happens when there is whitespace in a row or column. You can put the whitespace around the elements, in between them, center the items, or simply do nothing.

As for the items within that container:

  • .flex-home-item — this is a base class that all of the container items will inherit. It just sets them up so that there's spacing between them and that they all have the same height.
  • flex-grow — this determines how much the element should grow by when there is space available for it to grow into. Note that there are no units and that we're using small numbers: how the value of this property affects the elements its attached to depends on what the values of the other elements.

To expand on that second point: you'll note that we've set width and min-width values for our blocks; strictly, this isn't necessary (without them, they would size, initially, to accommodate their contents). However, they have obvious practical uses (eg a visually pleasing regularity) and the flex-grow values override them when necessary.

So I've created three different kinds of item: one regular sized, one wide, and one narrow. The base widths start at 15vw (for the narrow one) and then double each time we increase in size.

Now, if we weren't using a flex box layout, this would create very standard sizes and it would be very apparent that this was the case. But, as you remember, we set up the flex box and specified that we want elements to justify its content using the space around it (or, in other words, if there's space then use it). For the regular and narrow items, we've set these to 1: these thus form the 'baseline' growth level. However, we've set the wide one to grow by three times that.

This is arguably more apparent on mobiles. Even though we've specified the narrow blocks to be the same size, their true size is determined by the blocks next to it. So, for example, on mobile devices, block 2 and 7 will be different sizes because blocks 1 and 6 demand less/more space respectively. Indeed, if you toggle the minimum widths on the item classes in your browser, you'll see the effect it has on the items.

However, on larger screens, the size disparity is arguably less pronounced. Nonetheless, you'll notice that the items move around: wrapping onto new lines and adjusting their size to fill the gaps. This for me, is the essence of what makes flex boxes so powerful.

Actual Application

So, let's move on from colored blocks as they are not particularly useful. Instead, let's think about how we could use these blocks for promotional activity.

As I said earlier, if we are moving towards a magazine type of homepage and so we can use these blocks to promote certain things. My suggestion is that we use imagery in each, but with little or no text, depending on the size of the block. This is because of what will happen when the page is viewed on mobiles. Thus, I think something like the following is in order:

  • Wide items — let's treat these like carousel slides: background (ie subtle) imagery with descriptive text
  • Regular items — plain backgrounds with clear, concise text
  • Narrow items — standalone images that need no text

With that in mind, let's think about how we can construct this. The code that we use to construct the carousel on the homepage is a good example:

  1. Administrators use the backend configuration tool to specify each block that they want, including the background (image or color), type (regular, wide, narrow), text and link
  2. This is pulled into the view on the frontend and then sent to the context object
  3. The template loops through the items, generating the items with the supplied specifications

So let's put this into practice.

Expand the Module

Our current module setup is too basic to accommodate the changes we want to make, so let's expand it:

  • Add a folder called JavaScript
  • Put a basic entry point file in the JavaScript directory
  • Add a folder called Configuration
  • Update the ns.package.json file for these new folders
  • Update distro.json

The entry point file can look something like this:

define('OverrideHome'
, [
    'Home.View'
  , 'SC.Configuration'
  ]
, function (
    HomeView
  , Configuration
  )
{
  'use strict';

  return {

    mountToApp: function(application)
    {
      return true
    }
  }
});

Then we need to set up the configuration file as follows:

{
  "type": "object"

, "subtab": {
    "id": "flexboxhome"
  , "group": "layout"
  , "title": "Flexbox Home"
  }

, "properties": {
    "flexboxhome.items": {
      "group": "layout"
    , "subtab": "flexboxhome"
    , "title": "Flexbox Homepage Items"
    , "type": "array"
    , "items": {
        "type": "object"
      , "properties": {
          "type": {
            "type": "string"
          , "title": "Type"
          , "enum": ["regular", "wide", "narrow"]
          }
        , "background": {
            "type": "string"
          , "title": "Background"
          }
        , "isimage": {
            "type": "boolean"
          , "title": "Is Background an Image?"
          }
        , "text": {
            "type": "string"
          , "title": "Text"
          }
        , "link": {
            "type": "string"
          , "title": "Link"
          }
        }
      }
    }
  }
}

This'll collect the configuration data that we need in order to generate the boxes. By setting it up as an array that contains object, we will be able to iterate through them.

After you've made sure that you've updated all the right files, you'll need to undo the override for the homepage template (you wouldn't want to set your live site's homepage to a bunch of colored blocks would we?) and then deploy these up to your site.

When it's done, check Setup > SuiteCommerce Advanced > Configuration to see if it worked. If it has, we're ready to get going.

Build the Barebones of the Flexbox

Firstly, put back the override and start your local server again.

Then use the configuration tool to input the types of flexible items we want. For now, let's replicate close to what we have now so that we can test the barebones work. In the configuration for the flexbox, add in the items as they were in the template. You can then remove those items from the template.

Now that we have the configuration data, we need to pull it from the configuration object and feed it to the view for the homepage. If you head over to your local site's homepage, you can retrieve this data by typing SC.CONFIGURATION.flexboxhome.items into the developer console. You should see an array of objects. Now let's get this data plugged into the context object.

Open up OverrideHome.js and in the mountToApp function, put the following:

HomeView.prototype.getContext = _.wrap(HomeView.prototype.getContext, function(fn)
{
  var context = fn.apply(this, _.toArray(arguments).slice(1));
  context.flexboxItems = Configuration.flexboxhome.items;
  return context;
});

This is our standard code for adding new properties to the context object: take the getContext function, create a new one with our added properties, and then return it. If you want, you can put in a console log to check it's worked.

Now that we know we've got the data, we need to put something in our template. Replace the contents of override_home.tpl with the following:

<div class="flex-home">
    {{#each flexboxItems}}
        <a class="flex-home-item-{{type}}" style="background: {{background}};" href="{{link}}">{{text}}</a>
    {{/each}}
</div>

If you refresh your homepage, you'll see the familiar colored blocks return. By way of tidying up, remove the background declarations from the classes in _override-home.scss: we're setting these on a per-element basis now.

Styling the Items

From here we can start to think about the content of the blocks on the homepage. One of the things I said I'd like to do is put imagery in some of them.

On this blog, I've talked a few times about using imagery on the site including how to add them and why you should optimize them. For the sake of this tutorial, I'm going to use some free-to-use stock images.

As I said earlier, I'm only going to use them for items that are either wide or narrow, the regular blocks will have a plain background. Thus, in my current configuration I need 6 images. When deciding what size to make your images, consider the maximum height and width of the container you could expect the image displayed and at what point you'd be happy to lose some of its resolution as its stretched. For my site, I'm going to assume realistic maximum dimensions of 1500 x 500px for my wide images (or 3:1). As for the narrow ones, I'm assuming 400 x 500px (4:5)

When you've got your images ready, you'll need to put them in a folder called Images in the OverrideHome directory, and then modify the ns.package.json file to include them in the module:

"images": [
  "Images/*"
]

Naturally, we need to deploy this again so that we can upload the images. And, again, you'll need to remove the override so the colored blocks don't appear on your live site. So do that now and then run gulp deploy.

When that's done, put back the override, run your local server again and head back to your local homepage.

You can now test if your images have been uploaded by entering the URL for them. By default, images are uploaded to <domain name>/<absolute URL>/img/<file name>. The domain and file names are easy to know, but if you need to know your absolute URL then you can type _.getAbsoluteUrl(); into your console.

Now, head back to the backend configuration page as we need to add the image paths to the config data. In the appropriate Background fields, you'll need to add in the locations of the images but only in the format of img/<file name> (we do not need the fully qualified URLs).

Now this obviously causes a little bit of issue for our templates as the CSS declarations for colors and images are different: for images we need to append and prepend some markup. You'll note the configuration requires the user to specify is the background is an image: this is how we'll tell the template to produce different markup. Furthermore, in order to get the images to load, we will need the absolute URL in the template; so let's just run through the data and prepend it where necessary.

In OverrideHome.js, replace the declaration for context.flexboxItems with the following:

context.flexboxItems = _.map(Configuration.flexboxhome.items, function(item)
{
  if (item.background.split('/', 1) == 'img')
  {
    item.background = _.getAbsoluteUrl(item.background)
  }
  return item
});

What we're doing is qualifying the image URLs with the required absolute URL so that the site will load them. This code is a little hacky as it's checking to see if the value of each item's background property starts with 'img': thus we are assuming that the values entered by the administrator start with these values. We could blindly prepend the values with the absolute URLs but as the window is resized, this function will be run multiple times and we run the risk of broken URLs. For the purposes of this tutorial, this is OK, but you may want to find a more robust solution for your site.

After that, we need to edit the template. Replace the contents of override_home.tpl with the following:

<div class="flex-home">
    {{#each flexboxItems}}
        {{#if isimage}}
            <a class="flex-home-item-{{type}}" style="background-image: url('{{background}}');" href="{{link}}">{{text}}</a>
        {{else}}
            <a class="flex-home-item-{{type}}" style="background: {{background}};" href="{{link}}">{{text}}</a>
        {{/if}}
    {{/each}}
</div>

So you can see, it creates a loop for each of the flexbox items. If it's an image then it renders the markup required to show the image as a background, else it renders a 'normal' block.

With this code in place, we can then begin to update our configuration data to more suit what we want to achieve: eg, creating links to certain parts of the site, adding in text, etc, as well as make the necessary changes to the Sass. After some tinkering, I made my homepage look like this:

As you can see, it's quite attractive and there's clearly potential with each of the items.

Final Thoughts

That wraps up the tutorial aspect of this article. There are few things left to say.

Firstly, there are a lot more CSS properties available than the ones I have specified. For example, instead of specifying how much an item should grow, you can specify instead how much it should shrink. You can also change things up and specify that instead of rows, you want columns. Take a look at the list of new flex properties available as part of CSS3, as well as the associated ones (such as justify-content).

Next, remember what I said: this stuff is only usable by those with modern browsers. If you want to implement this on your site, you will need to have a serious discussion with the business owners and talk about the impact that cutting out users of old browsers will have. Alternatively, you could do some work to make this stuff only show to users of newer browsers, and maintain a separate homepage for them. How you serve them that page is up to you; we don't have any in-built detection and dynamic serving of content for users of different browsers. However, something like Modernizr could be implemented on your site to solve this problem.

Finally, if you don't want to use flexboxes because of the browser compatibility issue, then you could still make use of this design, but you'd have to use old-fashioned methods to achieve it. However, I hope you liked this look at what your site could be (if we didn't have to accommodate ancient browsers).

If you like, you can download a copy of my code here: OverrideHome@1.0.0.zip.