Styling the ECMASCript 6 Search Box (Part 3)

In part 1 of this series, I set up the basic framework (ASP.NET vNext with static files) and I set up a simple gulp script to load a couple of libraries. In part 2 I moved on to the actual search box Javascript implementation, writing it in ECMAScript 6, transpiling it and getting it to work with my AMD module loader, RequireJS. Next in the sequence is to get stylesheets working properly.

In my prior set of posts I settled on LESS for a preprocessor and Pleeease for a postprocessor of my stylesheets. The idea is that I will bundle all my stylesheets together into one big file and then include that file in my code. I chose LESS over SASS after experimentation. LESS has better support within Visual Studio and the SASS plugins for gulp don’t like to work with others – this is seen when you try to work with source maps.

Dynamic loading of stylesheets

Before I get to the big job of integrating LESS into my code, how about dynamic loading of style sheets? The RequireJS folks say it isn’t worth it. However I want to explore how I would do it. Let’s start with the requirements – I need require-css as a dependency in my package.json file – this is the RequireJS plug-in that is used to load CSS files. I need to include it in my build process, which I do by replacing my libraries:requirejs task with the following:

gulp.task('libraries:requirejs', function () {
    gulp.src(path.join(npmPath, 'requirejs/require.js'))
        .pipe(gulp.dest(path.join(buildPath, 'lib/requirejs')));
    gulp.src(path.join(npmPath, 'require-css/css.*'))
        .pipe(gulp.dest(path.join(buildPath, 'lib/requirejs')));
});

I also need to adjust my init.js file so that it knows about require-css – this is a part of the require.config section:

require.config({
    baseUrl: '/js',
    paths: {
        'jquery': '/lib/jquery/jquery.min',
        'bootstrap': '/lib/bootstrap/js/bootstrap.min',
        'require-css': '/lib/requirejs/css.min'
    },
    shim: {
        'bootstrap': { 
            deps: [
                'jquery'
            ] 
        }
    }
});

Finally, I remove the <link> for bootstrap from my test.html file and alter init.js so that it loads the file instead:

require([
    'jquery',
    'classes/MySearchBox',
    'bootstrap',
    'require-css!/lib/bootstrap/css/bootstrap.min'
], function ($) {

    $(document).ready(function () {
        $('#nav-search').searchbox();
    });

});

That was remarkably easy. Note that I called it require-css instead of just css. In a quirk of the plugin, if you have css in your path, it replaces the css with the path to your require-css plug-in. This mechanism avoids that problem. Of course, I can’t do that for ECMAScript 6 modules, so this is strictly a one-trick pony for loading dependent module CSS without putting the link in the HTML file.

Compiling LESS files

Moving on to my LESS files, I’m going to create a directory src/less to hold the LESS files. I’ve got a site.less file that contains all my major stuff:

@import (less) "reset.less";
@import (reference) "mixins.less";

@header-bg:	#666666;
@header-fg: white;

header {
	.size(100%; 50px);
	.colors(@header-bg; @header-fg);

	> nav {
		> ul {
			display: block;
			font-size: 24px;
			padding-top: 8px;

			> li {
				display: inline;
				padding: 4px 16px;
			}
		}
	}
}

This depends on two other files. The first is reset.less that is very simple, but I use it in every single project, so I just copy it from project to project:

html, body {
    width: 100%;
    height: 100%;
    margin: 0 !important;
    padding: 0 !important;
}

Since this is pure CSS you can use it as a SCSS file for inclusion in SASS stylesheets, as a LESS stylesheet or as a plain CSS stylesheet.

The second is my mixins.less file – this contains a few definitions that I like to use. I inevitably do height/width all the time, so it makes sense to combine them. I’ve got a whole bunch of mixins that I can select from – these are the only ones in use today:

.size(@width: 100%; @height: 100%) {
	height: @height;
	width: @width;
}

.colors(@background = white; @foreground = black) {
	background-color: @background;
	color: @foreground;
}

I have not touched the styling of the search box yet – I’ll get to that later. This is enough to get me started with my build process. In my Gulpfile.js I’ve added the gulp-less (don’t forget to add it to the package.json file):

// Gulp Plugins
var babel = require('gulp-babel'),
    del = require('del'),
    less = require('gulp-less'),
    merge = require('merge-stream'),
    path = require('path'),
    sourceMaps = require('gulp-sourcemaps');

I’ve also got my initial tasks defined:

gulp.task('source:compile-css', function () {
    gulp.src(['src/less/site.less'])
        .pipe(less())
        .pipe(gulp.dest(path.join(buildPath, 'css')));
});

gulp.task('source:clean-css', function (cb) {
    del([path.join(buildPath, 'css/**')], cb);
});

I’ve added these tasks to the build and clean tasks respectively. I have not added in the post-processor nor have I handled sourcemaps yet. However you can run this file and ensure your header looks right. This will ensure you have the less build process correctly created. The complete build process for my CSS looks like this:

gulp.task('source:compile-css', function () {
    gulp.src(['src/less/site.less'])
        .pipe(sourceMaps.init())
        .pipe(less())
        .pipe(please({
            autoprefixer: {
                browsers: ["> 5%", "last 3 versions"],
                cascade: true
            },
            filters: { oldIE: true },
            rem: false,
            pseudoElements: true,
            opacity: true,
            minifier: {
                preserveHacks: true,
                removeAllComments: false
            },
            mqpacker: false,
            calc: true
        }))
        .pipe(sourceMaps.write('.'))
        .pipe(gulp.dest(path.join(buildPath, 'css')));
});

Just like in the Javascript case, I’m going to use the gulp-sourcemaps plugin to generate the source map. This was one issue I had with gulp-sass when I was doing my experimentation – the source map was not generated properly. At the start of the pipe I say “ok – I want a source map”, then the plugins (which are compatible with gulp-sourcemaps) do their work, and finally I say “now write the source map file to the same directory as the CSS”.

I’ve included an expansive list of options to pleeease. I worked my way through the options and included some default as well – this just gives you an idea of how many post processors are out there. The important ones in this list are autoprefixer and minifier. Autoprefixer adds in vendor prefixes for the browsers you want to support (in this case, anything with a market share greater than 5% and the last 3 versions of the major browsers). Minifier does the compression in a similar manner to the Javascript minifier.

CSS for Components

I’ve still got this component that I want to style. I want it to be a reuseable component, so I want the CSS to be separated from my main application. I definitely want the LESS file to be kept with the component in the src/js/classes directory. Let’s create it first and worry about the integration afterwards. Here is my src/js/classes/MySearchBox.less file:

@import (reference) "css-shapes.less";

@search-back: white;
@search-border: #C0C0C0;
@search-box: @search-border - #333333;
@search-text: #666666;

.bubble {
    background-color: @search-back;
    border: 1px solid @search-border;
    border-radius: 8px;
    box-shadow: 2px 2px 2px 2px @search-box;
    .callout-triangle(20px; -16px; 16px; @search-border);

    .search {
        background: data-uri('search-32.png') no-repeat;
        padding-left: 40px;
        height: 32px;
        margin-top: 8px;
        margin-left: 8px;
        .callout-triangle(22px; -14px; 14px; @search-back);

        input {
            font-size: 18px;
            border: 0;
            margin-top: 2px;
            color: @search-text;
            outline: 0 !important;
        }
    }
}

Don’t forget to copy the search-32.png file into the same directory as MySearchBox.less.

I’ve included my css-shapes.less here – this is my library of CSS shapes that I’ve collected from Twitter. Notice that I’ve got a new mixin called .callout-triangle – that’s from the library. Here is its definition:

.callout-triangle(@left: 10px; @top: -10px; @size: 10px; @color: black) {
    &:before {
        content: '';
        position: absolute;
        left: @left;
        top: @top;
        height: 0;
        width: 0;
        border-left: @size solid transparent;
        border-right: @size solid transparent;
        border-bottom: @size solid @color;
    }
}

I’ve stored this library in the src/less directory as well – I’ll have to deal with the path issue in the build process, which is my next decision. I’ve basically got two options here:

  1. Bundle the component CSS with my site CSS so that I only load one file in the HTML document
  2. Bundle the CSS with the component and programmatically load the CSS into the page when it is used.

The common wisdom on the Internet seems to favor option 1 – bundling the component CSS with my site CSS. To do this, I need to grab all the less files in my source:compile-css task and output one merged CSS file:

gulp.task('source:compile-css', function () {
    gulp.src([ 'src/less/site.less','src/js/**/*.less' ])
        .pipe(sourceMaps.init())
        .pipe(less({
            paths: [ 'src/less' ]
        }))
        .pipe(please({
            autoprefixer: {
                browsers: ["> 5%", "last 3 versions"],
                cascade: true
            },
            filters: { oldIE: true },
            rem: false,
            pseudoElements: true,
            opacity: true,
            minifier: {
                preserveHacks: true,
                removeAllComments: false
            },
            mqpacker: false,
            calc: true
        }))
        .pipe(concat('site.css'))
        .pipe(sourceMaps.write('.'))
        .pipe(gulp.dest(path.join(buildPath, 'css')));
});

Note the concat at the end to call our resulting output site.css. Also check out the source map by using the F12 Developer Tools.

What about the alternative methodology of including the CSS with the component? In this case I would have to do the following:

  1. Create a new task that compiles the classes/*.css into wwwroot/js/classes
  2. Create a method in the MySearchBox class to load a CSS file
  3. In the MySearchBox constructor, detect if the CSS has already been loaded and if not then add it using the load a CSS file method

I obviously have not done this. There is a Google project called Polymer that aims to implement newer features of HTML within what is known as a polyfill – basically, backporting features so everyone can use them. In this case, the project includes a feature called Shadow DOM and you can use this to encapsulate the styling and Javascript inside of a simple element. Instead of adding a div element with all the included items, you can include a search-box element and the implementation is hidden. If I were composing a new element in the way I described above then I would likely use this technology.

In my next article I will cover the integration of testing and linting, so stay tuned. Until then, I’ve uploaded this code to my GitHub Repository for you to review. Note that it is the complete code, so check out the other articles in the series that cover everything.