Multi-tab slider in Umbraco with Zurb Foundation

This post is about the development of a sliding component built using Umbraco and the Foundation framework with sprinklings of javascript and css transitions. It was built for the re-development of leicester.gov.uk. The idea is that the component should show 3 panels by default with some key content showing in all. Then, to reveal additional content, each tab can be expanded, which in turns collapses the other tabs.

You can view a video of a beta version of it in action here, (I apologise for the creepy background music, blame late junction which was playing at the time I made the video)…

I had to make the component flexible so that higher level editors of the site could update it from the back office. So, the solution shown in the video allows users to add buttons to the panels, add up to 3 panels, move the buttons around, change the links, images used etc. It can contain multi-link buttons (the orange ones), buttons (blue ones), or rich text which can contain macros.

General styling

Since the multi-tab sliders can have either 2 or 3 tabs in, they must behave differently. If there are only 2 tabs, they must be 50% wide each. If its 3 tabs, the first tab must be 50% and the others 25% each. This is because the first tab in the panel is, by convention, the most important. So, a general class is generated based on how many tabs there are. This is then targeted in the SCSS…

&.mts-tab-count-3 {
  .tab {
    @media #{$medium-up} {
      &.expanded {   //1
        @include grid-column(10);
      }
      &.collapsed {   //2
        @include grid-column(1);
      }
    }

  }
  .tab0 {
    @media #{$medium-up} {
      @include grid-column(6);   //3
    }
    @media #{$large-up} {
      padding-right: $global-padding * $tab-padding-factor!important;
    }
  }
  .tab1 {
    @media #{$medium-up} {
      @include grid-column(5);  //4
    }
    @media #{$large-up} {
      @include grid-column(3);
      padding: 0 $global-padding * $tab-padding-factor!important;
    }
  }
  .tab2 {
    border-right: none;
    &.collapsed {
      @media #{$medium-only} {
        @include collapsed-tab-header();
      }
    }
    @media #{$medium-up} {      //5
      @include grid-column(1);
    }
    @media #{$large-up} {
      @include grid-column(3);
      padding-left: $global-padding * $tab-padding-factor!important;
    }
  }
  .tab1,
  .tab2 {
    @media #{$large-up} {
      @include grid-column(3);     
    }
  }

}

So, here’s what the above is all about…

  1. each tab, when it has the class of ‘expanded’ applied to it expands to fill 10 columns.
  2. When it’s collapsed, it only covers 1 column.
  3. the first tab (tab0), by default and on medium screens and up spans 6 columns
  4. the second tab (tab1) spans 5 columns on medium screens and 3 columns on large screens
  5. the third tab spans 1 column on medium screens and 3 on large. So for tablets, the third column is shown as collapsed.

However, each tab, regardless of its context will have general settings.  This includes stuff like setting the width of each tab to animate. This means that when the column width is changed, it animates to the new width rather than just popping. Generic values for when the tab is collapsed or expanded are also set. So, any collapsed tab should hide all child content and show the vertical heading. For this, again, a keyframe css animation is used.

Keyframe animation

Here’s the code…

&.collapsed {
//...

@media #{$medium-up}{
  @include collapsed-tab-header();
  .multi-tab-slider-heading {
    h2 {
      .no-csstransitions & {
        display: none;
      }
    }
  }
 }
}

So this calls the ‘collapsed-tab-header’ mixin and for browsers that don’t support css transitions, it simply hides the header. The call to the mixin calls this line.

@include animate(rotateDownRight, 0.5s);

…which is itself a call to a mixin. So, for animations, I created a mixin that would accept a type and duration. The actual keyframe animation has to be defined manually (annoyingly). Not sure if later versions can compile keyframe animations (I’m on 3.2.5).

The animate mixin sets up values for duration, fill mode, animation count and sets the name.

@keyframes rotateDownRight {
  0% {
    transform-origin: left bottom;
    transform: rotate(0);
  }

  100% {
    transform-origin: left bottom;
    transform: rotate(90deg);
  }
}

Ignoring all the vendor prefixes you have to put in (there are a lot), the rotateDownRight animation sets the animated element to rotate 90 degrees down from the left bottom corner.

Styles for expanded/collapsed elements

Each of the buttons in the tabs are laid out in block grids. This means they have a good vertical and horizontal flow. However, a question arises. On a large screen, a tab in its default state may accommodate 3 columns quite happily. But, when that tab is expanded, it needs to show 5 columns. Then, if the screen size is reduced, the expanded tab should only show 3 columns again. This could all be manually styled of course, but that would be some major fudge! I decided to follow foundations example and create classes that could be added to mark-up, determining how they behave in different states.

Expanded-medium-block-grid-3 etc

Foundation, out of the box, has classes for block grids. So, if you want a block grid that shows 1 column for small screens and 3 for medium you write

<div class="small-block-grid-1 medium-block-grid-3"></div>

So, I extended this to create classes for block grids inside expanded tabs…

&.expanded {
  //...
  @media #{$small-up}{
    @include expander-grid-html-classes(small);
    @include expander-block-grid-html-classes(small);
  }
  @media #{$medium-up}{
    @include expander-grid-html-classes(medium);
    @include expander-block-grid-html-classes(medium);
  }
  @media #{$large-up}{
    @include expander-grid-html-classes(large);
    @include expander-block-grid-html-classes(large);
  }
}

So, what’s going on here?! Inside elements with a class of ‘expanded’ classes are defined for small, medium and large screens. This applies to both block grids and just normal grids. Here’s the mixin its calling…

@mixin expander-block-grid-html-classes($size) {
  @for $i from 1 through $block-grid-elements {
    .expanded-#{$size}-block-grid-#{($i)} {
        @include block-grid($i);
    }
  }
}

So this just loops through the number of columns set in the settings and outputs a block grid. So, there is nothing fundamentally new, its all building on foundations block grid classes.

Where’s the javascript?

Since the behaviour of the elements is all defined in css, it means we don’t have to rely massively on javascript. The only thing that javascript has to do is set the ‘expanded/collapsed’ classes on the tabs that are selected/deselected and css handles everything else, including the animations, column sizes, element wrapping etc.

Creating a multi-tab slider in the back-office

When a user needs to create/update one of the sliders, there is a system folder where multi-tab sliders can be created. Inside these, one of three types of element can be created (multi-tab buttons, multi-tab multi-link buttons, multi-tab rich text). These elements are then just configured like any other document type. Because its this standard functionality, ordering, removing, queuing stuff is a doddle. From the actual page to apply the multi-tab slider, there is simply a multi-node tree picker where the user can drag and drop a multi-tab slider tab. This allows easy re-ordering of the tabs.

Umbraco Cms Multi-tab slider

Summary

That about wraps it up. There is some other stuff, and if anyone is interested, please ask. This was a good exercise in digging into foundation, and thinking about how to create components that can be managed by editors (with common sense) without the phone ringing all the time!

As always, comments, suggestions, ideas most welcome.

Leave a Reply