Integrating SASS and Bootstrap to Ember Components

One of the first things we notice when we get started with Ember is that our app/styles folder has no CSS at all.

We want a responsive framework that works well with Ember and is straightforward to install. Can we use Bootstrap? What’s the best method to integrate it into our app?

Since we’re all about being productive, how about SASS?

Sassy Ember

Let’s move to our existing app directory (or create a new one like ember new awesome, for example).

Easy-peasy SASS installation:

$ ember install ember-cli-sass

We simply rename app.css to app.scss (note the SCSS extension) and try it out:

// app/styles/app.scss

body {

  $main-color: gold;

  h2 {
    color: $main-color;
  }

}

At localhost:4200, our “Welcome to Ember” title (or any other h2 for that matter) turned into gold!

SASS just works. We can even @import other files.

Strap my Ember boots, baby

There are two approaches to install Bootstrap. Manually or using an add-on.

The bower package method is super easy, so we’ll do that first:

$ bower install bootstrap --save

The second step is telling Ember CLI to include Bootstrap resources (CSS, JS, fonts) in the generated output. For this we’ll open ember-cli-build.js and configure it:

// ember-cli-build.js

var EmberApp = require('ember-cli/lib/broccoli/ember-app');

module.exports = function(defaults) {
  var app = new EmberApp(defaults, {
    // Add options here
  });

  app.import(app.bowerDirectory + '/bootstrap/dist/css/bootstrap.css');
  app.import(app.bowerDirectory + '/bootstrap/dist/js/bootstrap.js');
  app.import(app.bowerDirectory + '/bootstrap/dist/fonts/glyphicons-halflings-regular.woff', {
    destDir: 'fonts'
  });

  return app.toTree();
};

That should be enough to get Bootstrap up and running! (You may want to add additional font files.)

By the way, that’s the gist of how to include any resource in our app’s generated output.

Front page

We want to obtain a Bootstrappy looking app, like this:

The front page is our first goal. Later on we’ll get to a Dashboard.

Most HTML/CSS hereforth is borrowed from the Bootstrap Dashboard example.

Step 1. Include some SASS specific to our design:

// app/styles/app.scss

/*
 * Base structure
 */

/* Move down content because we have a fixed navbar that is 50px tall */
body {
  padding-top: 50px;
}


/*
 * Global add-ons
 */

.sub-header {
  padding-bottom: 10px;
  border-bottom: 1px solid #eee;
}

/*
 * Top navigation
 * Hide default border to remove 1px line.
 */
.navbar-fixed-top {
  border: 0;
}

/*
 * Sidebar
 */

/* Hide for mobile, show later */
.sidebar {
  display: none;
}
@media (min-width: 768px) {
  .sidebar {
    position: fixed;
    top: 51px;
    bottom: 0;
    left: 0;
    z-index: 1000;
    display: block;
    padding: 20px;
    overflow-x: hidden;
    overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
    background-color: #f5f5f5;
    border-right: 1px solid #eee;
  }
}

/* Sidebar navigation */
.nav-sidebar {
  margin-right: -21px; /* 20px padding + 1px border */
  margin-bottom: 20px;
  margin-left: -20px;
}
.nav-sidebar {
  > li {
    > a {
      padding-right: 20px;
      padding-left: 20px;
    }
  }

  > .active {
    > a, > a:hover, > a:focus {
      color: #fff;
      background-color: #428bca;
    }
  }

}

/*
 * Main content
 */

.main {
  padding: 20px;

  .page-header {
    margin-top: 0;
  }
}
@media (min-width: 768px) {
  .main {
    padding-right: 40px;
    padding-left: 40px;
  }
}

Step 2. Modify our application template to include the navigation bar:

{{! app/templates/application.hbs }}

{{nav-bar}}

{{outlet}}

Right, we are using the application template as our main layout. Remember that {{outlet}} is where the output of nested routes gets placed.

Step 3. Create the nav-bar component:

$ ember generate component nav-bar

Let’s include the navbar from Bootstrap in our component’s template:

{{! app/templates/components/nav-bar.hbs }}

<nav class="navbar navbar-inverse navbar-fixed-top">
  <div class="container-fluid">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a href="#" class="navbar-brand">Awesomeproject</a>
    </div>
    <div id="navbar" class="navbar-collapse collapse">
      <ul class="nav navbar-nav navbar-right">
        <li>
          <a href="#" class="nav-item nav-link">Dashboard</a>
        </li>
        <li>
          <a href="#" class="nav-item nav-link">Settings</a>
        </li>
      </ul>
      <form class="navbar-form navbar-right">
        <input type="text" class="form-control" placeholder="Search...">
      </form>
    </div>
  </div>
</nav>

Step 4. A route named index will be served at /:

$ ember generate route index

We’ll copy-paste the jumbotron Bootstrap example:

{{! app/templates/index.hbs }}

<div class="jumbotron">
  <div class="container">
    <h1>Hello, world!</h1>
    <p>This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique.</p>
    <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more »</a></p>
  </div>
</div>

<div class="container">
  <!-- Example row of columns -->
  <div class="row">
    <div class="col-md-4">
      <h2>Heading</h2>
      <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
      <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
    </div>
    <div class="col-md-4">
      <h2>Heading</h2>
      <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
      <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
   </div>
    <div class="col-md-4">
      <h2>Heading</h2>
      <p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
      <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
    </div>
  </div>

  <hr>

  <footer>
    <p>© 2016 Company, Inc.</p>
  </footer>
</div> <!-- /container -->

Presto! How does it look now?

Ember best practices delivered straight to your inbox? Tell me where:

(One e-mail every month. No BS. Unsubscribe anytime!)

Dashboard

Our first version of the dashboard will look like:

The red lines simply reveal two components that we’ll have to create (side-bar and dashboard-main).

Step 1. First off, the dashboard route!

$ ember generate route dashboard

Yup, it’s bound to the /dashboard URL.

Meanwhile in the template…

{{! app/templates/dashboard.hbs }}

<div class="container-fluid">
  <div class="row">
    <div class="col-sm-3 col-md-2 sidebar">
      {{side-bar}}
    </div>
    <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
      {{dashboard-main articles=model}}
    </div>
  </div>
</div>

So, we are passing in model to a component? Where does that data come from?

You guessed right, the route’s model hook is loading some (static, dummy) data as our model:

// app/routes/dashboard.js

export default Ember.Route.extend({

  model() {
    return [
      ["1,001", "Lorem", "ipsum", "dolor", "sit"],
      ["1,002", "amet", "consectetur", "adipiscing", "elit"],
      ["1,003", "Integer", "nec", "odio", "Praesent"],
      ["1,003", "libero", "Sed", "cursus", "ante"],
      ["1,004", "dapibus", "diam", "Sed", "nisi"],
      ["1,005", "Nulla", "quis", "sem", "at"],
      ["1,006", "nibh", "elementum", "imperdiet", "Duis"],
      ["1,007", "sagittis", "ipsum", "Praesent", "mauris"],
      ["1,008", "Fusce", "nec", "tellus", "sed"],
      ["1,009", "augue", "semper", "porta", "Mauris"],
      ["1,010", "massa", "Vestibulum", "lacinia", "arcu"],
      ["1,011", "eget", "nulla", "Class", "aptent"],
      ["1,012", "taciti", "sociosqu", "ad", "litora"],
      ["1,013", "torquent", "per", "conubia", "nostra"],
      ["1,014", "per", "inceptos", "himenaeos", "Curabitur"],
      ["1,015", "sodales", "ligula", "in", "libero"]
    ];
  }

});

Step 2. The necessary components:

$ ember generate component side-bar
$ ember generate component dashboard-main

Yet again, the sidebar is really just Bootstrap HTML:

{{! app/templates/components/side-bar.hbs }}

<ul class="nav nav-sidebar">
  <li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li>
  <li><a href="#">Reports</a></li>
  <li><a href="#">Analytics</a></li>
  <li><a href="#">Export</a></li>
</ul>
<ul class="nav nav-sidebar">
  <li><a href="">Nav item</a></li>
  <li><a href="">Nav item again</a></li>
  <li><a href="">One more nav</a></li>
  <li><a href="">Another nav item</a></li>
  <li><a href="">More navigation</a></li>
</ul>
<ul class="nav nav-sidebar">
  <li><a href="">Nav item again</a></li>
  <li><a href="">One more nav</a></li>
  <li><a href="">Another nav item</a></li>
</ul>

The dashboard component, however, receives data through its property articles:

{{! app/templates/components/dashboard-main.hbs }}

<h1 class="page-header">Dashboard</h1>

<h2>Articles</h2>
<div class="table-responsive">
  <table class="table table-striped">
    <thead>
      <tr>
        <th>#</th>
        <th>Header</th>
        <th>Header</th>
        <th>Header</th>
        <th>Header</th>
      </tr>
    </thead>
    <tbody>
      {{#each articles as |article|}}
      <tr>
        <td>{{ article.[0] }}</td>
        <td>{{ article.[1] }}</td>
        <td>{{ article.[2] }}</td>
        <td>{{ article.[3] }}</td>
        <td>{{ article.[4] }}</td>
      </tr>
      {{/each}}
    </tbody>
  </table>
</div>

(Did you notice? That is exactly how we access array values by index!)

Loading our app at localhost:4200/dashboard we see everything works as it should!

Need Font Awesome?

$ ember install ember-font-awesome

In your templates:

{{! random hbs file }}

<span>{{fa-icon "star" size="lg"}}</span>

More info here.

Bootstrap components in the dashboard

Right above the table we will include an “instructions” div, along with a button that toggles it:

To start implementing these updates, we’ll insert the following HTML/Handlebars into our dashboard component:

{{! app/templates/components/dashboard-main.hbs }}

<h1 class="page-header">Dashboard</h1>

<button id="toggle" type="button" class="btn btn-default" {{action 'collapse'}}>
  <span class="glyphicon glyphicon-book" aria-hidden="true"></span> Instructions
</button>

<div id="instructions" class="panel-collapse collapse">
 <div class="well">
   <h2>Instructions</h2>
   <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
 </div>
</div>

<h2>Articles</h2>
<div class="table-responsive">
  <table class="table table-striped">

  ...

A few noteworthy things happening here with the button:

We need to activate the tooltip and handle the collapse action. That will all go in the component’s Javascript:

// app/components/dashboard-main.js

export default Ember.Component.extend({

  actions: {
    collapse() {
      this.$('#instructions').collapse('toggle');
    }
  },

  didInsertElement() {
    this.$('#toggle').tooltip({ title: "Click here to toggle instructions" });
  },

  willDestroyElement() {
    // remember to clean up your room, kids
    this.$('#toggle').tooltip('destroy');
  }

});

The implementation is pretty self-explanatory, isn’t it?

In order to have proper in-app navigation, we choose to update links using Ember’s link-to helper:

{{! app/templates/components/nav-bar.hbs }}

<nav class="navbar navbar-inverse navbar-fixed-top">
  <div class="container-fluid">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      {{link-to "Awesomeproject" 'index' class='navbar-brand'}}
    </div>
    <div id="navbar" class="navbar-collapse collapse">
      <ul class="nav navbar-nav navbar-right">
        <li>
          {{link-to "Dashboard" 'dashboard' class='nav-item nav-link'}}
        </li>
        <li>
          <a href="#" class="nav-item nav-link">Settings</a>
        </li>
      </ul>
      <form class="navbar-form navbar-right">
        <input type="text" class="form-control" placeholder="Search...">
      </form>
    </div>
  </div>
</nav>

Now that we can easily navigate between screens, let’s try it out! It should all be working like a charm.

The coolest Bootstrap library

It’s called ember-bootstrap and it ships with native Ember Bootstrap components!

$ ember install ember-bootstrap

Remember to remove the added Bootstrap imports at ember-cli-build.js before using ember-bootstrap!

The styling should prevail because ember-bootstrap uses the exact same bootstrap bower package.

In order to understand what native components are all about, let’s replace our instructions div with the bs-collapse provided by the add-on:

{{! app/templates/components/dashboard-main.hbs }}

<h1 class="page-header">Dashboard</h1>

<button id="toggle" type="button" class="btn btn-default" {{action 'collapse'}}>
  <span class="glyphicon glyphicon-book" aria-hidden="true"></span> Instructions
</button>

{{#bs-collapse collapsed=collapsed}}
  <div id="instructions">
   <div class="well">
     <h2>Instructions</h2>
     <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
   </div>
  </div>
{{/bs-collapse}}

<h2>Articles</h2>
<div class="table-responsive">
  <table class="table table-striped">

  ...

Notice that collapsed property? Native components play very well with Ember idioms. Adapting our component’s JS file…

// app/components/dashboard-main.js

export default Ember.Component.extend({

  collapsed: true,

  actions: {
    collapse() {
      this.toggleProperty('collapsed');
    }
  }

});

(Unfortunately, we had to remove tooltips as ember-bootstrap does not yet support them.)

And that wraps up our article! We only scratched the surface of everything these powerful libraries offer.

Were you able to make it work? Did you create anything awesome with it? Let me know in the comments below!

Enjoyed this article? Don't miss my next one!

Leave me your e-mail for content that will help you master Ember:

Do you want to master Ember fast?

Leave me your e-mail for helpful updates delivered straight to your inbox.

(A few e-mails per month. No BS. Unsubscribe anytime!)