How to Apply Themes to Sails Applications, Part 1

Luis Lobo Borobia

September 29th, 2016

The Sails Framework is a popular MVC framework that is designed for building practical, production-ready Node.js apps. Themes customize the look and feel of your app, but Sails does not come with a configuration or setting for handling themes by itself. This two-part post shows one of the ways you can set up theming for your Sails application, thus making use of some of Sails’ capabilities.

You may have an application that needs to handle theming for different reasons, like custom branding, licensing, dynamic theme configuration, and so on. You can adjust the theming of your application, based on external factors, like patterns in the domain of the site you are browsing.

Imagine you have an application that handles deliveries that you customize per client. So, your app renders the default theme when browsed as http://www.smartdelivery.com, but when logged in as a customer, let's say, "Burrito", it changes the domain name as http://burrito.smartdelivery.com.

In this series we make use of Less as our language to define our CSS. Sails already handles Less right out of the box. The default Less file is located in /assets/styles/importer.less. We will also use Bootstrap as our base CSS Framework, importing its Less file into our importer.less file.

The technique showed here consists of having a base CSS, and a theme CSS that varies according to the host name.

Step 1 - Adding Bootstrap to Sails

We use Bower to add Bootstrap to our project. First, install it by issuing the following command:

npm install bower --save-dev

Then, initialize the Bower configuration file.

node_modules/bower/bin/bower init

This command allows us to configure our bower.json file. Answer the questions asked by bower.

? name sails-themed-application
? description Sails Themed Application
? main file app.js
? keywords
? authors lobo
? license MIT
? homepage
? set currently installed components as dependencies? Yes
? add commonly ignored files to ignore list? Yes
? would you like to mark this package as private which prevents it from being accidentally published to the registry? No

{
  name: 'sails-themed-application',
  description: 'Sails Themed Application',
  main: 'app.js',
  authors: [
    'lobo'
  ],
  license: 'MIT',
  homepage: '',
  ignore: [
    '**/.*',
    'node_modules',
    'bower_components',
    'assets/vendor',
    'test',
    'tests'
  ]
}

This generates a bower.json file in the root of your project.

Now we need to tell bower to install everything in a specific directory. Create a file named .bowerrc and put this configuration into it:

{"directory" : "assets/vendor"}

Finally, install Bootstrap:

node_modules/bower/bin/bower install bootstrap --save --production

This action creates a folder in assets named vendor, with boostrap inside of it. Since Bootstrap uses JQuery, you also have a jquery folder:

├── api
│   ├── controllers
│   ├── models
│   ├── policies
│   ├── responses
│   └── services
├── assets
│   ├── images
│   ├── js
│   │   └── dependencies
│   ├── styles
│   ├── templates
│   ├── themes
│   └── vendor
│       ├── bootstrap
│       │   ├── dist
│       │   │   ├── css
│       │   │   ├── fonts
│       │   │   └── js
│       │   ├── fonts
│       │   ├── grunt
│       │   ├── js
│       │   ├── less
│       │   │   └── mixins
│       │   └── nuget
│       └── jquery
│           ├── dist
│           ├── external
│           │   └── sizzle
│           │       └── dist
│           └── src
│               ├── ajax
│               │   └── var
│               ├── attributes
│               ├── core
│               │   └── var
│               ├── css
│               │   └── var
│               ├── data
│               │   └── var
│               ├── effects
│               ├── event
│               ├── exports
│               ├── manipulation
│               │   └── var
│               ├── queue
│               ├── traversing
│               │   └── var
│               └── var
├── config
│   ├── env
│   └── locales
├── tasks
│   ├── config
│   └── register
└── views

We need now to add Bootstrap into our importer. Edit /assets/styles/importer.less and add this instruction at the end of it:

@import "../vendor/bootstrap/less/bootstrap.less";

Now you need to tell Sails where to import Bootstrap and JQuery JavaScript files from.

Edit /tasks/pipeline.js and add the following code after it loads the sails.io.js file:

// Load sails.io before everything else
  'js/dependencies/sails.io.js',
  // <ADD THESE LINES>
  // JQuery JS
  'vendor/jquery/dist/jquery.min.js',
  // Bootstrap JS
  'vendor/bootstrap/dist/js/bootstrap.min.js',
  // </ADD THESE LINES>

Now you have to edit your views layout and pages to use the Bootstrap style. In this series I created an application from scratch, so I have the default views and layouts.

In your layout, insert the following line after your tag:

<link rel="stylesheet" href="/themes/<%= typeof theme == 'undefined' ? 'default' : theme %>.css">

This loads a second CSS file, which defaults to /themes/default.css, into your views.

As a sample, here are the /views/layout.ejs and /views/homepage.ejs I changed (the text under the headings is random text):

/views/layout.ejs
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
  <title><%= typeof title == 'undefined' ? 'Sails Themed Application' : title %></title>
  <!--STYLES-->
  <link rel="stylesheet" href="/styles/importer.css">
  <!--STYLES END-->
  <!-- THIS IS WHERE THE THEME CSS IS LOADED -->
  <link rel="stylesheet" href="/themes/<%= typeof theme == 'undefined' ? 'default' : theme %>.css">
</head>
<body>
<%- body %>
<!--TEMPLATES-->

<!--TEMPLATES END-->
<!--SCRIPTS-->
<script src="/js/dependencies/sails.io.js"></script>
<script src="/vendor/jquery/dist/jquery.min.js"></script>
<script src="/vendor/bootstrap/dist/js/bootstrap.min.js"></script>
<!--SCRIPTS END-->
</body>
</html>

Notice the lines after the <!--STYLES END--> tag.

/views/homepage.ejs
<nav class="navbar navbar-inverse navbar-fixed-top">
  <div class="container">
    <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 class="navbar-brand" href="#">Project name</a>
    </div>
    <div id="navbar" class="navbar-collapse collapse">
      <form class="navbar-form navbar-right">
        <div class="form-group">
          <input type="text" placeholder="Email" class="form-control">
        </div>
        <div class="form-group">
          <input type="password" placeholder="Password" class="form-control">
        </div>
        <button type="submit" class="btn btn-success">Sign in</button>
      </form>
    </div><!--/.navbar-collapse -->
  </div>
</nav>

<!-- Main jumbotron for a primary marketing message or call to action -->
<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 &raquo;</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 &raquo;</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 &raquo;</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 &raquo;</a></p>
    </div>
  </div>

  <hr>

  <footer>
    <p>&copy; 2015 Company, Inc.</p>
  </footer>
</div> <!-- /container -->

You can now lift Sails and see your Bootstrapped Sails application.

Now that we have our Bootstrapped Sails app set up, in Part 2 we will compile our theme’s CSS and the necessary Less files, and we will set bup the theme Sails hook to complete our application.

About the author

Luis Lobo Borobia is the CTO at FictionCity.NET, is a mentor and advisor, independent software engineer consultant, and conference speaker. He has a background as a software analyst and designer, creating, designing, and implementing Software products and solutions, frameworks, and platforms for several kinds of industries. In the last years he has focused on research and development for the Internet of Things, using the latest bleeding-edge software and hardware technologies available.