Adding a bootstrap template to an ember application

Bootstrap 4

Let´s create a new ember application and style it with a free Bootstrap 4 admin template.

Source code and live demo

Download the complete source code of this example here

https://github.com/digiquence/ember3-bootstrap4-template

Live Demo

https://digiquence.github.io/ember3-bootstrap4-template/

Choosing a template

Sometimes you need to build and show quickly an application with a professional look, but you don’t have a style sheet yet. There are many good templates available to download and use for free. We will use an admin template called Stellar, a great option for a typical web application because it’s fully responsive, easy to customize, looks good and is free. Preview and download it here.

Our application will follow the structure of the bootstrap template: Header, footer, menu on the left, and the main area in the center.

Goals

  • Set up our app templates in four areas according to the template structure.
  • Install the template style sheet and JavaScript code.
  • Send a user name and a version number from the ember application and display it on the header and footer respectively.
  • Create a custom list of menu items, display it on the menu area and assign a route to each item.

Initial setup

I’ll assume you have already installed nodejs, npm, and ember-cli on your system and cloned or downloaded the Stellar template.

Building the bootstrap template

According to the instructions that come with Stellar, you must build the template before using it. To do so, go to your local Stellar installation directory and run npm install

$ cd <path-to-stellar>
$ npm install

It can take a few minutes to download all the dependencies, be patient. When the build is finished you can open index.html on your browser and navigate through the example pages.

Depending on the template, this step is not always strictly necessary for your ember app, but if you try to navigate trough the examples before building the template, the layout may look broken.

Bear in mind that every template is different, therefore, all come with their own particular set of instructions. Some are ready to use and do not need to be built at all. It is recommended to always take a look to the README file of your template.

Creating a new ember application

In this example we will use the most recent ember-cli version (3.8.1 at the time of writing this post) to create a new ember application called ember3-bootstrap4-template.

$ ember --version
ember-cli: 3.8.1

$ cd <path-to-my-projects-directory>
$ ember new ember3-bootstrap4-template
$ cd ember3-bootstrap4-template

Installing plugins

Stellar comes with a lot of plugins but we don’t need them all, we will only use the following:

  • Bootstrap 4
  • Font Awesome
  • Simple line icons

Install Bootstrap

Let’s install the ember-cli addon ember-bootstrap

$ ember install ember-bootstrap

Install Font Awesome

There is also an ember-cli addon to use Font Awesome icons in ember applications: @fortawesome/ember-fontawesome.

$ ember install @fortawesome/ember-fontawesome 

Please notice this is not the same as ember-font-awesome, which is a different addon.

Font Awesome version 5 has different icon sets. Some of them are free, others are available only to pro subscribers. To use this ember-cli addon we have to install the icon sets we need. Let’s install the free icons of solid and regular sets only. You can install more icon sets later.

$ npm i --save-dev @fortawesome/free-solid-svg-icons
$ npm i --save-dev @fortawesome/free-regular-svg-icons

Install Simple line icons

The template menu uses Simple line icons. If you don’t want to show icons on your menu you can skip this part, or you can always use the Font Awesome icons instead since we already have installed them. However I’ll show how to use Simple line icons so our app looks exactly like the template demo.

Unfortunately, there is no ember-cli addon for this icon set so we will have to install them manually.

$ npm install simple-line-icons --save

This dependency is installed in /node_modules/simple-line-icons and added to the package.json file:

  "dependencies": {
    "simple-line-icons": "^2.4.1"
  }

Also, we must include simple-line-icons.css in our ember-cli-build.js file

'use strict';

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

module.exports = function(defaults) {
  let app = new EmberApp(defaults, {
    'ember-bootstrap': {
      'bootstrapVersion': 4,
      'importBootstrapFont': false,
      'importBootstrapCSS': true
    }
  });

  app.import('node_modules/simple-line-icons/css/simple-line-icons.css');

  return app.toTree();
};

Finally, we must copy the font files to the public folder of the ember app

$ cp -R node_modules/simple-line-icons/fonts/ public/fonts

Dissecting the HTML code

The template has many sample pages, but, to avoid distractions, let’s start with the empty page <path-to-stellar>/pages/samples/blank-page.html

We need to identify where the code for the header, footer, and menu begins and ends, then create a component for each of them. Of course, we could throw all the code in our main application template /app/templates/application.hbs but in the long run, the code is easier to maintain if it is well separated.

$ ember g component app-footer
$ ember g component app-header
$ ember g component app-menu

Now let’s extract the HTML code for each section. The code we need is inside the <body> tag

  <!-- MAIN CONTAINER START -->
  <div class="container-scroller">

    <!-- HEADER START -->
    <nav class="navbar col-lg-12 col-12 p-0 fixed-top d-flex flex-row">
      <!-- MORE HEADER CODE... -->
    </nav>
    <!-- HEADER END -->

    <!-- INNER CONTAINER START -->
    <div class="container-fluid page-body-wrapper">
      <div class="row row-offcanvas row-offcanvas-right">

        <!-- MENU START -->
        <nav class="sidebar sidebar-offcanvas" id="sidebar">
            <!-- MORE MENU CODE... -->
        </nav>
        <!-- MENU end -->        
               
        <!-- CENTRAL SECTION WRAPPER START -->
        <div class="content-wrapper">
        </div>
        <!-- CENTRAL SECTION WRAPPER END -->
    
        <!-- FOOTER START -->
        <footer class="footer">
            <!-- MORE FOOTER CODE... -->
        </footer>
        <!-- FOOTER END -->

      </div>
    </div>
    <!-- INNER CONTAINER END -->

  </div>
  <!-- MAIN CONTAINER END -->

The main <div> container will live in the main template and here we include the component for each section

app/templates/application.hbs

{{! MAIN CONTAINER START }}
<div class="container-scroller">

    {{! HEADER START }}
    {{app-header tagName="nav" class="navbar col-lg-12 col-12 p-0 fixed-top d-flex flex-row"}}
    {{! HEADER END }}

    {{! INNER CONTAINER START }}
    <div class="container-fluid page-body-wrapper">
        <div class="row row-offcanvas row-offcanvas-right">

            {{! MENU START }}
            {{app-menu tagName="nav" class="sidebar sidebar-offcanvas" id="sidebar"}}
            {{! MENU end }}

            {{! CENTRAL SECTION WRAPPER START }}
            <div class="content-wrapper">
                {{outlet}}
            </div>
            {{! CENTRAL SECTION WRAPPER END }}

            {{! FOOTER START }}
            {{app-footer tagName="footer" class="footer"}}
            {{! FOOTER END }}

        </div>
    </div>
    {{! INNER CONTAINER END }}

</div>
{{! MAIN CONTAINER END }}

Now we add the code for each component and remove unneeded stuff from each of them.

app/templates/components/app-header.hbs

<div class="text-center navbar-brand-wrapper">
    {{#link-to "index" class="navbar-brand brand-logo"}}
    <img src="images/logo.svg" alt="logo">
    {{/link-to}}
    {{#link-to "index" class="navbar-brand brand-logo-mini"}}
    <img src="images/logo_mini.svg" alt="logo">
    {{/link-to}}
</div>
<div class="navbar-menu-wrapper d-flex align-items-center">
    <p class="page-name d-none d-lg-block">{{fa-icon "user"}} {{userName}}</p>
</div>

Copy the logos from the template to the public directory of your application inside a new sub directory called images:

$ mkdir public/images
$ cp <path-to-stellar>/images/logo.svg ./public/images
$ cp <path-to-stellar>/images/logo_mini.svg ./public/images

app/templates/components/app-footer.hbs

<div class="container-fluid clearfix">
    <span class="text-muted d-block text-center text-sm-left d-sm-inline-block">
        Copyright © 2019
        <a href="http://www.bootstrapdash.com/" target="_blank">Bootstrapdash</a>. All rights reserved.
    </span>
    <span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">
        {{fa-icon "cube"}} version {{versionNumber}}
    </span>
</div>

app/templates/components/app-menu.hbs

<ul class="nav">
    <li class="nav-item nav-category">
        <span class="nav-link">GENERAL</span>
    </li>
    {{#each menuItems as |menuItem|}}
    <li class="nav-item">
        {{#link-to menuItem.route class="nav-link"}}
        <span class="menu-title">{{menuItem.label}}</span>
        <i class="{{menuItem.icon}} menu-icon"></i>
        {{/link-to}}
    </li>
    {{/each}}
</ul>

Dissecting the CSS style sheet

We need to import the CSS style sheet from the bootstrap template. You can find it here: <path-to-stellar>/css/style.css

Unfortunately, for this particular template, if you copy it unchanged to the vendor directory and import it to your project, you will notice that it doesn’t look exactly like the template demo, so we will need to make some adjustments.

The problem of the style sheet at this point is that it tries to import some fonts from google but the web page does not load them. The reason is that CSS import declarations must appear at the beginning of the file. Therefore, if there are previous declarations, then the imports will be ignored.

When ember-cli builds the application, many CSS files are loaded into one. In this case, the import statements from the template CSS end up in the middle of the generated file, that’s why they don’t work.

But that’s not the only problem. Upon inspecting style.css we can see that it includes an old version of Bootstrap. It would be much better if we could use whatever future version of Bootstrap when we want, instead of this fixed version. We can do that.

The file style.css has three sections we are interested in:

  • Start
  • Old Bootstrap version
  • End

We will delete completely the code of the embedded bootstrap, then we will save the start and end sections, each to a new file in the vendor directory:

vendor/style-start.css
vendor/style-end.css

Then we will import these files along with the new version of bootstrap we already installed in our project, and we must do it in the same order:

  • Start
  • Current Bootstrap version
  • End

Our ember-cli-build.js now looks like this:

'use strict';

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

module.exports = function(defaults) {
  let app = new EmberApp(defaults, {
    'ember-bootstrap': {
      'bootstrapVersion': 4,
      'importBootstrapFont': false,
      'importBootstrapCSS': false
    }
  });

  // Stylesheet part 1
  app.import('vendor/style-start.css');
 
  // Bootstrap
  app.import('node_modules/bootstrap/dist/css/bootstrap.css');

  // Stylesheet part 2
  app.import('vendor/style-end.css');

  // Simple line icons
  app.import('node_modules/simple-line-icons/css/simple-line-icons.css');
  
  return app.toTree();
};

Important: Notice we have changed the ember-bootstrap configuration to set ‘importBootstrapCSS’ to false because we are now importing the bootstrap CSS manually.

Finally, let’s not forget about importing the google fonts. Remember this must be the first declaration of a CSS file, so we can safely add it to the main style sheet file of the ember app, styles/app.css and the fonts will be imported correctly.

@import url("https://fonts.googleapis.com/css?family=Lobster|Open+Sans:300,400,700|Roboto:400,500,700");

Routes, models, and templates

The application has the Bootstrap template installed, so now it’s time to create a list of menu items and assign a route to each of them.

Let’s start generating three new routes. They will be added automatically to routes.js:

$ ember g route forms
$ ember g route buttons
$ ember g route typography

Next, let’s create the menu-item model. It will need three properties: the route name, the icon name, and the label.

ember g model menu-item route:string icon:string label:string

models/menu-item.js

import DS from 'ember-data';

export default DS.Model.extend({
    route: DS.attr('string'),
    icon: DS.attr('string'),
    text: DS.attr('string')
});

We will assign the list of menu items, the user name, and the version number to the model of the main application route, routes/application.js.

import Route from '@ember/routing/route';
import RSVP from 'rsvp';

export default Route.extend({
    model() {
        var store = this.store;
        var allMenuItems = [];

        allMenuItems.push(store.createRecord("menu-item", { id: 1, route: "forms", icon: "icon-flag", label: "Forms" }));
        allMenuItems.push(store.createRecord("menu-item", { id: 2, route: "buttons", icon: "icon-grid", label: "Buttons" }));
        allMenuItems.push(store.createRecord("menu-item", { id: 3, route: "typography", icon: "icon-pencil", label: "Typography" }));

        return RSVP.hash({
            menuItems: allMenuItems,
            userName: "Raul",
            versionNumber: "1.0.0"
        });
    },
});

We are almost finished, there is only one more thing left to do. If you run the application you’ll not see yet the menu items, the user name or the version number.

The header, footer and menu components expect the data as input parameters. We must add those parameters when calling each component in the main template.

templates/application.hbs

{{! MAIN CONTAINER START }}
<div class="container-scroller">

    {{! HEADER START }}
    {{app-header tagName="nav" class="navbar col-lg-12 col-12 p-0 fixed-top d-flex flex-row" userName=model.userName}}
    {{! HEADER END }}

    {{! INNER CONTAINER START }}
    <div class="container-fluid page-body-wrapper">
        <div class="row row-offcanvas row-offcanvas-right">

            {{! MENU START }}
            {{app-menu tagName="nav" class="sidebar sidebar-offcanvas" id="sidebar" menuItems=model.menuItems}}
            {{! MENU end }}

            {{! CENTRAL SECTION WRAPPER START }}
            <div class="content-wrapper">
                {{outlet}}
            </div>
            {{! CENTRAL SECTION WRAPPER END }}

            {{! FOOTER START }}
            {{app-footer tagName="footer" class="footer" versionNumber=model.versionNumber}}
            {{! FOOTER END }}

        </div>
    </div>
    {{! INNER CONTAINER END }}

</div>
{{! MAIN CONTAINER END }}

Testing the application

To test the application so far, I added the example forms that come with the template. Everything worked as expected except for the following: checkboxes and radio buttons were missing as well as some icons.

Upon inspecting the sample pages I found the reasons. First, they use a set of icons we do not have in our application. Second, there is a piece of JavaScript code that initializes the checkboxes and radio buttons that our application is missing.

We can substitute the icons easily with others from Font Awesome. Then we only have to add the missing JavaScript code at the correct place. In this case, we need to make sure the web page has been rendered or otherwise the initialization code might not find the widgets to initialize.

In ember words, this means: When the route has completed successfully the transition, schedule a function call with the desired initializing JavaScript code to run only once after the page has finished rendering. We will add this code to the main route (app/routes/application.js) to make sure radio buttons and checkboxes can be added safely on any page.

    actions: {
        didTransition() {
            scheduleOnce('afterRender', this, () => {
                $(".form-check label,.form-radio label").append('<i class="input-helper"></i>');
            });
        },
    }

And that’s it!. You can always add any extra initializing code in a similar fashion.

Conclusion

There is no one-size-fits-all solution to add a template to an ember application because,as I said before, every template is different. You have to understand the structure of the HTML code, the CSS style sheet and how the template uses plugins, but more importantly, which features you really need and which can be discarded, and then install them correctly so you have full control over them.

I wanted to keep this post simple using only plain CSS. However, as an alternative, you can use SASS via the ember-cli addon ember-cli-sass. I’ll write a post about it in the near future.

One more thing…

I noticed a few slight differences between the Bootstrap version included in the template and the one installed with ember-bootstrap. For example, the button colors are not exactly the same, and the height of the form components is calculated differently. This could make your pages not look as you expected. To fix this you can always add your own customization rules to your /styles/app.css like the following:

@import url("https://fonts.googleapis.com/css?family=Lobster|Open+Sans:300,400,700|Roboto:400,500,700");

.btn-primary {
    background-color: #24d5d8;
    border-color: #24d5d8;
}

.btn-secondary {
    background-color: #aab2bd;
    border-color: #aab2bd;
}

.btn-info {
   background-color: #6569df;
    border-color: #6569df;
}

.form-control {
    height: unset;
}

.form-check .form-check-label .input-helper::after {
    content: '\2713';
}

.form-radio.form-radio-flat label input:checked + .input-helper::after {
    content: '\2022';
    top: -15px;
    left: -1px;
    font-size: 2.5em;    
}

.list-ticked li::before {
    content: '\2713';
    color: #fd3258;
}

.list-arrow li::before {
    content: '>';
    color: #33c92e;
}

.list-star li::before {
    content: '\2605';
    color: #fed961;
}

.list-ticked li::before, .list-arrow li::before, .list-star li::before {
    font-family: unset;
}

Leave a Reply

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