Sunday, July 1, 2012

Inheritance and Variables in CSS with Less

I always missed object oriented inheritance and variables in CSS. Style sheets could be much more readable and maintainable, if they would have ability to say:
  • I want this selector color to be just like that one.
  • I want this class to be just like that one, with one simple change.

As multiple projects run into CSS explosion and maintenance problems problems, it was only question of time until someone comes with another solution. The original solution came from the Ruby world and is quite simple and elegant:
  • extend CSS with needed features,
  • compile extended CSS into regular CSS,
  • serve the result to the browser.

The browser does not have to deal with a new syntax. It was given standard CSS file and will process it as usually. On the other hand, the programmer had variables, inheritance and few other features available.

This post introduces multiple extended CSS languages, picks up one and shows how to use it. The next post shows how to use wro4j library to integrate the compiler into java web project.

Extended CSS Languages

At least three different extended CSS languages are available:
We decided to use LESS, because its integration into Java project is easiest. Its compiler was written in JavaScript and is able to run either directly in the browser or on the server using Rhino.

Stylus was also written in JavaScript, but was meant for node.js environment. Slightly modified version can run either from Java or directly in the browser. The browser compliant solution is part of grails plugin. If you want to use it, you can dug it out of there.

SASS was the first extended CSS language out there and its compiler was written in ruby. It had also an alternative JavaScript compiler sass.js, but the project is not maintained anymore. Last commit was done 2 years ago and its owner started the Stylus project.

If you want to read more about these languages, an excellent side-by-side comparison is available on net.tuts+ site.

Less Introduction

LESS was originally developed by Alexis Sellier alias cloudhead. It is still actively maintained and developed. The project has also multiple committers and numerous contributors.

Its source code is hosted on Github and distributed under Apache License 2.0 license.

This chapter shows how to create a simple project that compiles .less files during the page load on the browser. That simple project is then used to show the most important less features.

Sample Project
Less can run entirely in the browser, so you can experiment with it on a simple HTML page. Running it this way is practical for small HTML and JavaScript projects or if you have not decided whether you want to use it or not.

Of course, running the compiler in the browser slows down the page load and does not work if the JavaScript is disabled. If one of these is a concern, then you will have to move to compiler to the server side.

Overview
Using less on the browser side is quite simple:
  • Load all .less style sheets using link tag.
  • Load the compiler using script tag.

All .less files have to be loaded before the compiler script is loaded:
<head>
  <link rel="stylesheet/less" type="text/css" href="main-theme.less">
  ...
  <link rel="stylesheet/less" type="text/css" href="other-theme.less">
  <script src="less.min.js" type="text/javascript"></script>
</head>

Sample .less File
Start with the sample .less file from the less main page. It has enough of less features in it, so we can use it to test whether the less compiler works.

Create new directory and place theme.less file in it:
@base: #38b9ab;

.box-shadow(@style, @c) when (iscolor(@c)) {
  box-shadow:         @style @c;
  -webkit-box-shadow: @style @c;
  -moz-box-shadow:    @style @c;
}
.box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) {
  .box-shadow(@style, rgba(0, 0, 0, @alpha));
}
.box { 
  color: saturate(@base, 5%);
  border-color: lighten(@base, 30%);
  div { .box-shadow(0 0 5px, 30%) }
}

Install Less Compiler
Download less.js and place it into the same directory. Rename it to less.min.js.

Sample HTML File
Create sample html file and place it to the same directory. Our sample html file loads theme.less file:
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <link rel="stylesheet/less" type="text/css" href="theme.less">
  <script src="less.min.js" type="text/javascript"></script>
  <title>Less Demo</title>
</head>
<body>
  <div class="box">
 Box: both text and border colors are functions of the @base variable. <br> The border is 30% lighter and the text is 5% saturated.
    <div>
 A div inside a box has a grey shadow. 
   </div> 
  </div>
  <p>No CSS available for this element.</p>
</body>
</html>

Test It
Open HTML file in the browser. Less compiler compiles .less style sheet during the page load and compiled css is applied to the HTML.

Everything inside the 'box' div is blue and the inner div has grey shadow.

Less Basics
This sub-chapter shows the most important less features. We are not going to go into details, less documentation does a good job at that. We describe only what is needed to understand .less file used in previous chapter.

Almost all sections show one less feature and contains less style sheet example. Example files are compatible with simple project created in previous chapter. You can replace theme.less file content with any one and see how the page changes.

All examples used in this chapter are available in a Github repository named less.js-demo.

Error Reporting
If the .less file is invalid, Less compiler shows incorrect line along with its line number on top of the screen. This is makes debugging style sheets much easier.

CSS Compatibility
Less extends CSS and makes style sheets more dynamic. Any valid .css file is also a valid .less file.

Less compiler supports most style sheet hacks, but not necessarily all of them. For example, IE 6 only hack from the style sheet hacks article does not work:
_bac\kground: #f60;

The good new is that whenever someone demanded a new hack, the project maintainer responded fast and added missing hacks into the less. Therefore, if you need the above construction for some reason, it is quite possible that you are going to get it.

Note: we did not reported the missing IE 6 only hack as a bug, because we do not know whether someone actually needs it.

Nesting
Nesting means, that a rule can be placed inside another rule. The main advantage of this construct is a scope. Nested rule can use any variable or mixin defined inside its parent rules. Both variables and mixins are explained in following sections.

If we forget about scoping, rule nesting is just another way how to express descendant selectors. A simple nested rule:
.box { 
  /* box properties */
  display:inline-block; 
  padding: 4px;  
  div { 
    /* div inside box properties */
    padding-left: 4px;
    padding-right: 4px;
  }
}

is equivalent to following .css:
.box { 
  /* box properties */
  display:inline-block; 
  padding: 4px;  
}
.box div { 
  /* div inside box properties */
  padding-left: 4px;
  padding-right: 4px;
}

Variables
Variables work the same way as constants in any other language. They serve as a symbolic name for a value that can not change. They are prefixed with @ and usable only within a block where they have been declared.

The next snipped shows how to use variables to define consistent padding. The .box class defines a local @defPad variable. It contains default padding size and is usable only inside the .box and its nested .box div rule. New example contains also a global variable @base which could be used anywhere.

The example is inside theme-variables.less file:
/* unused global variable */
@base: #38abf9;

.box {
  /* local variable */
  @defPad: 4px;
  display:inline-block;
  padding: @defPad; 
  div {
    /* div inside box properties */
    padding-left: @defPad;
    padding-right: @defPad;
  }
}

/* @defPad is NOT available here */

The compiled style sheet:
.box {
    display: inline-block;
    padding: 4px;
}
.box div {
    padding-left: 4px;
    padding-right: 4px;
}

Functions and Operators
Operators and functions add computational power to style sheets. You can use them to define a color to be slightly lighter than another one or padding twice as big as another one.

Each operator works on both numbers and colors. Functions usually work with either numbers or colors.
  • operators: +, - *, /,
  • math functions: round, ceil, floor, percentage,
  • color functions: darken, desaturate, mix, ... .

The complete list of color functions is too long and available in less documentation.

The following code uses color functions to define similar colors for border, text and inner elements. The code is located in theme-functions-operators.less file:
@base: #38abf9;
.box {
  border: 2px solid lighten(@base, 30%);
  color: saturate(@base, 5%);
  div {
    color: fade(@base, 30%)
  }
}

The compiled style sheet:
.box {
    border: 2px solid #CDEAFD;
    color: #33ACFE;
}
.box div {
    color: rgba(56, 171, 249, 0.3);
}

Mixins
Mixins are css rules that can be reused inside other rules. Use them to avoid repetition when you need the same set of properties inside multiple rules.

For example, each browser uses different property to paint shadow around an element. We placed all three browser dependent properties into the box-shadow rule. The rule is used to add shadow to both .box and .box div.

The example is located inside the theme-simple-mixin.less file:
.box-shadow {
  box-shadow:         0 0 5px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
  -moz-box-shadow:    0 0 5px rgba(0, 0, 0, 0.3);
}

.box {
  .box-shadow;
  div {
    .box-shadow;
  }
}

The compiled style sheet:
.box-shadow {
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
    -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
    -moz-box-shadow:    0 0 5px rgba(0, 0, 0, 0.3);
}
.box {
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
    -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
    -moz-box-shadow:    0 0 5px rgba(0, 0, 0, 0.3);
}
.box div {
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
    -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
    -moz-box-shadow:    0 0 5px rgba(0, 0, 0, 0.3);
}

As you can see, the mixin was copied inside the other rules. Other than that, it was treated like any other rule in the style sheet and copied into the resulting style sheet. If you do not want to have it there and want to treat it only as a helper rule, place () after the mixin declaration.

The example is located inside the theme-clean-mixin.less file:
.box-shadow() {
  box-shadow:         0 0 5px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
  -moz-box-shadow:    0 0 5px rgba(0, 0, 0, 0.3);
}

.box {
  .box-shadow;
  div {
    .box-shadow;
  }
}

The compiled style sheet does not contain class:
.box {
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
    -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
    -moz-box-shadow:    0 0 5px rgba(0, 0, 0, 0.3);
}
.box div {
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
    -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
    -moz-box-shadow:    0 0 5px rgba(0, 0, 0, 0.3);
}

Parametrized Mixins
A mixin can be parametrized. Parameters do not have types and act as local variables inside the mixin. Mixin can have any number of parameters and each can have a default value.

Note: the theme-clean-mixin from the previous section is an example of a parametrized mixin with empty list of variables.

The .box-shadow mixin in the next example uses @style and @color parameters to customize shadows around various elements. The default @color is black. The final theme-parametrized-mixin.less file adds green shadow around the .box: and red shadow around the .box div:
.box-shadow(@style, @color: rgb(0, 0, 0)) {
  box-shadow:         @style @color;
  -webkit-box-shadow: @style @color;
  -moz-box-shadow:    @style @color;
}

.box {
  .box-shadow(0 0 7px, rgba(0, 90, 0, 0.9));
  div {
    .box-shadow(0 0 7px, rgba(90, 0, 0, 0.9));
  }
}

The compiled style sheet:
.box {
    box-shadow: 0 0 7px rgba(0, 90, 0, 0.9);
    -webkit-box-shadow: 0 0 7px rgba(0, 90, 0, 0.9);
    -moz-box-shadow: 0 0 7px rgba(0, 90, 0, 0.9);
}
.box div {
    box-shadow: 0 0 7px rgba(90, 0, 0, 0.9);
    -webkit-box-shadow: 0 0 7px rgba(0, 90, 0, 0.9);
    -moz-box-shadow: 0 0 7px rgba(0, 90, 0, 0.9);
}

Guarded Mixins
Guards are conditions attached to mixins. A guarded mixin is used only if its condition is satisfied. This allows you to have multiple mixins with the same name and number of parameters. It also simulates if statement.

Guards conditions are quite powerful and ofter a lot of possibilities. They can use all standard math and color functions and operators. They can use also a set of boolean operators and functions (less then, equals, isColor, isNumber, ...).

Guard is a keyword when followed by a condition. Most of them simply check mixin parameters types.

The next example defines two mixins with the same name and same number of parameters. One is used if the second parameter is color and another is used if the second parameter is number:
/* define shadow with supplied color */
.box-shadow(@style, @c) when (iscolor(@c)) {
  box-shadow:         @style @c;
  -webkit-box-shadow: @style @c;
  -moz-box-shadow:    @style @c;
}
/* define black shadow with supplied transparency */
.box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) {
  .box-shadow(@style, rgba(0, 0, 0, @alpha));
}

.box {
  .box-shadow(0 0 7px, rgba(0, 90, 0, 0.9));
  div {
    .box-shadow(0 0 7px, 30%);
  }
}

The compiled style sheet:
.box {
    box-shadow: 0 0 7px rgba(0, 90, 0, 0.9);
    -webkit-box-shadow: 0 0 7px rgba(0, 90, 0, 0.9);
    -moz-box-shadow: 0 0 7px rgba(0, 90, 0, 0.9);
}
.box div {
    box-shadow: 0 0 7px rgba(0, 0, 0, 0.3);
    -webkit-box-shadow: 0 0 7px rgba(0, 0, 0, 0.3);
    -moz-box-shadow: 0 0 7px rgba(0, 0, 0, 0.3);
}

All in One
The following code has all the above features in it. In fact, it is slightly changed showcase taken from the less homepage.
@base: #38abf9;

.box-shadow(@style, @c) when (iscolor(@c)) {
  box-shadow:         @style @c;
  -webkit-box-shadow: @style @c;
  -moz-box-shadow:    @style @c;
}

.box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) {
  .box-shadow(@style, rgba(0, 0, 0, @alpha));
}

.box { 
  @defPad: 4px;

  display:inline-block; 
  border: 2px solid lighten(@base, 30%);
  color: saturate(@base, 5%);
  padding: @defPad;
  div { 
    padding-left: @defPad;
    padding-right: @defPad;
    .box-shadow(0 0 5px, 30%);
 }
} 

It is compiled into following .css file:
.box {
  display: inline-block;
  border: 2px solid #cdeafd;
  color: #33acfe;
  padding: 4px;
}
.box div {
  padding-left: 4px;
  padding-right: 4px;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
  -moz-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}

Server Side Integration

Running less compiler in the browser is fine as long as the page load speed does not matter. Once the speed becomes important, it is better to move the compiler on the server.

First sub-chapter lists links to resources useful if you want to run less.js from the command line. The following one contains quick overview of Java libraries that help to bring less.js on the server side.

Command Line
Less.js runs on node.js. If you have node.js installed on *nix system, you can use lesscss script to run it from command line.

Windows users can avoid node.js installation using the less.js-windows project. It is a handy command line wrapper over less.js and uses built-in Windows JavaScript runner.

In Java
As of now, less has only JavaScript compiler. There are two initiatives to create a pure java compiler, but they are in very early stage. The standard way is to use Rhino as a JavaScript engine and run less.js in it.

There are multiple libraries that already do that. If you aim for simplicity, you may use lescss. Another solution is in visural-common and visural-wicket libraries and Rhino based compiler-only project exists too.

Finally, any Java project can use wro4j. It is quite a large library that integrates not only less.js, but also a lot of other potentially useful technologies.

To Be Continued
The next post will show how to use wro4j library to integrate the extended CSS compiler into java project. We picked up wro4j, because it seems to be the most mature, has the most users and offers additional nice features once you have it in your project.

1 comments:

lesscss said...

thanx for ur help....

Post a Comment