Creating a collapsible sidebar page with jide.js

There is an almost unlimited amount of useful or interesting controls you could create with jide.js. This time, we’ll create a page layout that has a collapsible sidebar. This type of control is extremely common for mobile applications, where screen space is a limited resource.

As always, you can find the finished control in my jidejs-extra repository.

Now let’s get to work.

What do we need?

SidebarPage with collapsed sidebar

SidebarPage with collapsed sidebar


This is the very first question you should ask yourself when creating a new control. What kind of properties does it need? How much does the view model (a.k.a. the Skin) need to do? For this control, we need the following:

  • content: The content of the page
  • sidebar: The sidebar that should be shown
  • title: A title that should be shown at the top of the page

Our Skin will probably need to know the width of the sidebar (to transform the content position) and it should know whether or not the sidebar should be visible.

The Control and its Skin

define([
  'jidejs-extra/Class',
  'jidejs/base/Observable',
  'jidejs/base/DOM',
  'jidejs/ui/Control',
  'jidejs/ui/Skin',

  'text!./SidebarPage.html'
], function(
  Class, Observable, DOM,
  Control, Skin,
  SidebarPageTemplate
) {
  var Property = Class.Property;
  var SidebarPage = Class({ // we're using jidejs-extra/Class
    $extends: Control,

    $init: function(config) {
      Control.call(this, config || {});
      this.classList.add('jide-extra-sidebar-page');
    },

    sidebar: Property,
    content: Property,
    title: Property
  });
  SidebarPage.Skin = Skin.create(Skin, {
    template: SidebarPageTemplate,
    isSidebarVisibleProperty: false,

    install: function() {
      // store the toggled state here so we can use it in the template
      this.isSidebarVisibleProperty = Observable(false);
      Skin.prototype.install.call(this);
    },

    get sidebarWidth() { // calculate the offset of the content
      return this.isSidebarVisible ? DOM.measure(this['x-sidebar']).width : 0;
    },

    // just to make the template more readable, it'd have been shorter to use .get() but anyway...
    get isSidebarVisible() {
      return this.isSidebarVisibleProperty.get();
    },

    set isSidebarVisible(value) {
      this.isSidebarVisibleProperty.set(value);
    },

    toggleSidebarVisibility: function() {
      this.isSidebarVisible = !this.isSidebarVisible;
    }
  });
  return SidebarPage;
});

The above code is quite simple. If you’ve got any questions, just leave a comment and I’ll clearify.

The Template

<template>
  <aside pseudo="x-sidebar" bind="
    content: component.sidebar,
    css: {
      'is-visible': isSidebarVisible
    }
  "></aside>

  <section pseudo="x-main" bind="
    style: {
      '-webkit-transform': 'translate3d('+sidebarWidth+'px, 0, 0)',
      '-moz-transform': 'translate3d('+sidebarWidth+'px, 0, 0)',
      '-ms-transform': 'translate3d('+sidebarWidth+'px, 0, 0)',
      '-o-transform': 'translate3d('+sidebarWidth+'px, 0, 0)',
      'transform': 'translate3d('+sidebarWidth+'px, 0, 0)'
    }
  ">

    <header pseudo="x-header">
      <svg pseudo="x-toggle" width="30" height="24" bind="
        on: {
          click: toggleSidebarVisibility.bind($item)
        }
      ">

        <circle fill="#34495e" cx="2.5" cy="2.5" r="2.5"/>
        <circle fill="#34495e" cx="2.5" cy="12" r="2.5"/>
        <circle fill="#34495e" cx="2.5" cy="21.5" r="2.5"/>
        <rect x="7.2" fill="#34495e" width="19.8" height="5"/>
        <rect x="7.2" y="9.5" fill="#34495e" width="22.8" height="5"/>
        <rect x="7.2" y="19" fill="#34495e" width="16.8" height="5"/>
      </svg>
      <h1 bind="content: component.title"></h1>
    </header>
    <section pseudo="x-content" bind="
      content: component.content
    "></section>

  </section>
</template>

Okay, so this is slightly more complicated. As you can see, we’re using an inline SVG icon for the toggle “button” as well as a couple of binding expressions.

The advantage of using binding expressions is that they update automatically whenever a property that they rely on is changed. So, when we use the Skins sidebarWidth property with a style binding, the styles will be updated whenever the sidebarWidth property changes. Since sidebarWidth uses the isSidebarVisible property, it will be updated whenver the sidebars visibility is toggled.

The CSS/LESS

html, body {
  margin: 0;
  padding: 0;
}

.jide-extra-sidebar-page {
  width: 100%;

  & > .x-sidebar {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    #padding > .large();
    .box-shadow(inset -1.5em 0 1.5em -0.75em rgba(0, 0, 0, 0.25));

    &.is-visible {
      .transform(translate3d(0%, 0, 0));
    }

    .transform(translate3d(-100%, 0, 0));

    -webkit-backface-visibility: hidden;
      -moz-backface-visibility: hidden;
      -ms-backface-visibility: hidden;
      -o-backface-visibility: hidden;
      backface-visibility: hidden;

    -webkit-transition: -webkit-transform 500ms ease;
      -moz-transition: -moz-transform 500ms ease;
      -o-transition: -o-transform 500ms ease;
      transition: transform 500ms ease;
  }

  & > .x-main {
    -webkit-transition: -webkit-transform 500ms ease;
      -moz-transition: -moz-transform 500ms ease;
      -o-transition: -o-transform 500ms ease;
      transition: transform 500ms ease;

      & > .x-header {
        width: 100%;
        #padding > .large();

        & > .x-toggle, & > h1 {
          vertical-align: middle;
          display: inline-block;
          margin: 0 (@unit*2);
        }
      }

      & > .x-content {
        #padding > .large();
      }
  }

  & > .x-sidebar, & > .x-main > .x-header {
    #background > #solid > .inverse();
  }

  & > .x-main > .x-header > .x-toggle {
    & > circle, & > rect {
      fill: @button-text;
    }

    &:hover {
      & > circle, & > rect {
        fill: fade(@button-text, 75%);
      }
    }
  }
}

Okay. That’s a lot of styling but it’s not that bad, right? We use a transition on the transform property to create an animation and we use the transform property to move the sidebar off-screen and back when we don’t need it.

Putting it all together

Now let’s create a simple example application using this new control.

require([
  'jidejs/ui/control/Label',
  'jidejs-extra/control/SidebarPage'
], function(Label, SidebarPage) {
  document.body.appendChild(new SidebarPage({
    sidebar: new Label({ text: 'Sidebar' }),
    content: new Label({ text: 'Content' }),
    title: 'Sidebar Page Demo'
  }).element);
});

Note that since we’ve used the content binding in our template, the value of each of the properties (sidebar, content and title) could be either a string, jide.js control or HTML element. I’ve chosen to use a label here, but it’d of course make more sense to create a navigation control and bind it’s currently active page to the content. The Yeoman Quickstart contains an example of how to do something like that. Don’t forgot to add custom styling to your ListView, though.

Leave a Reply

Your email address will not be published. Required fields are marked *