Decoupling CSS Files (Skinning)

It can be very useful to decouple CSS files from each other without reusing classes.  In some cases you might want a designer to focus on handling the coloring items and not have control over positioning.  This can be very painful especially if most of your CSS classes have already been defined.

This methodology allows you to dynamically create some “Skin” classes based off predefined ones.  I have created two simple helper functions.  This tutorial requires that you use jQuery.

function applySkin() {
    _skinObjs($("body"));    
    _skinObjs($("div"));
    _skinObjs($("span"));    
    _skinObjs($("a"));
}
 
function _skinObjs(objs) {
    for (var i = 0; i < objs.length; i++) $(objs[i]).addClass($(objs[i]).attr("class") + "-Skin");    
}

This will essentially, add a “–Skin” class to every body, div, span, and anchor tag on your page (feel free to add additional tags as necessary). 

Now you can remove what information you want repeated.  So let’s say that you have a Site.css stylesheet and you want to remove some stylistic definitions to a separate file (coloring for example).  To continue on that example, take this sample html code.

<html>
<head>
<title>Sample Page</title>
<link href="Site.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
</head>
<body>
<div class="MainTitle">Hello World!</div>
</body>
</html>

 

Take a look at the Site.css, notice how the color information is in the same file.

.MainTitle 
{
    font-size: 32pt;
    position: absolute;
    top: 100px;
    left: 100px;
    background-color: #fea130;
}

So what I need to do now is to create a separate file.  Let’s call it Site-Skin.css.  I will append to my class “-Skin” as seen earlier.  I also need to remove it from my Site.css file.

Updated Site.css

.MainTitle
{
    font-size: 32pt;
    position: absolute;
    top: 100px;
    left: 100px;
}

 

Site-Skin.css

.MainTitle-Skin
{
    background-color: #fea130;
}

Now I need to specify in my HTML code the link to the particular “-Skin” I want to use.  Let’s say that I’m using a scripting language like ASP.NET, I could put the skins in different folders and dynamically change the decoupled CSS file.

<%@ Page Language="C#" %>
<html>
<head>
<title>Sample Page</title>
<link href="Site.css" rel="stylesheet" type="text/css" />
<link href="<%=Skin_Folder%>/Site-Skin.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
</head>
<body>
<div class="MainTitle">Hello World!</div>
</body>
</html>

 

Now if you look below you can see my folder structure has multiple Site-Skin.css files.

image

This is very powerful as your designer can very easily work on the Skin while you work / or someone else works on positioning and other features of the styling process.

Technorati Tags: ,,,
12.14.2010 13:58 by Danny Gershman | Comments (0) | Permalink

Observer pattern in Javascript

The observer pattern is extremely powerful in many cases where you want to have a Javascript class “listen” to another object.  The class or object attaches to a Subject and waits an update command and then notifies all Observers.    Another key concept here is the idea of inheritance in Javascript. 

I have a simple Javascript function that handles inheritance:

function inherits(base, extension)
{
    for (var property in base)
    {
        try
        {
            extension[property] = base[property];
        }
        catch(warning)
        {
        
        }
    }
}

 

Using this concept in mind I can now extend any additional functionalities of any Javascript class or object on the DOM.  Now let’s take a look at the Observer and Subject classes.

function Observer()
{
    this.Update = function(data)
    {
        alert(this.id + " Update() function has not been implemented");
    }
}
 
function Subject() {
    this.observers = new ArrayList();
}
 
Subject.prototype.Notify = function( context ) {
    var m_count = this.observers.Count();
            
    for( var i = 0; i < m_count; i++ )
        this.observers.GetAt(i).Update( context );
}
 
Subject.prototype.AddObserver = function( observer ) {
    if( !observer.Update )
        throw 'Wrong parameter';
 
    this.observers.Add( observer );
}
 
Subject.prototype.RemoveObserver = function( observer ) {
    if( !observer.Update )
        throw 'Wrong parameter';
   
    this.observers.RemoveAt(this.observers.IndexOf( observer, 0 ));
}

 

We can now go forward and actually implement the code on our HTML page.  Let say we have a series of form fields and we want to set that data equal to the value of another text box.  We could easily subscribe or unsubscribe to listen for updates.  We’ll then use a simple HTML button to send updates to the listening controls.

Our page looks like this in the browser

image

And now for the underlying HTML code.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>    
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
    <script type="text/javascript" src="../Resources/Javascripts/library.js"></script>
    <title>Observer Pattern Example</title>
</head>
<body>
    <form>
        <br />textbox1: <input type="text" id="textbox1" />
        <br />button2: <input type="button" id="button2" />
        <br />notify data: <input type="text" id="textbox3" />
        <input type="button" id="button1" value="Notify" onclick="NotifyObservers(this)" />
    </form>
</body>
</html>
    <script type="text/javascript">
        $(function() {
            inherits(new Observer(), $("#textbox1")[0]);
            inherits(new Observer(), $("#button2")[0]);
            inherits(new Subject(), $("#button1")[0]);
 
            $("#button1")[0].AddObserver($("#textbox1")[0]);
            $("#button1")[0].AddObserver($("#button2")[0]);
 
            $("#textbox1")[0].Update = function(data) {
                this.value = data;
            }
 
            $("#button2")[0].Update = function(data) {
                this.value = data;
            }
        });
 
        function NotifyObservers(subject) {
            subject.Notify($("#textbox3")[0].value);
        }
    </script>

 

As you can see the DOM objects themselves inherit the respective Javascript class functions and properties.  I’m using jQuery to directly call the functions on the objects.  When I fill in some value on the textbox3 that data gets passed through the individual implementations of the Update() method on each object that had been attached to the Subject.

image

The attached objects have decided to update their own values, but in theory I can do anything with that data.  A very powerful design pattern.

This also uses an ArrayList a feature not included in Javascript.  The ArrayList class is seen below.

function ArrayList() {
    this.aList = []; //initialize with an empty array
}
        
ArrayList.prototype.Count = function() {
    return this.aList.length;
}
        
ArrayList.prototype.Add = function( object ) {
    return this.aList.push( object ); //Object are placed at the end of the array
}
 
ArrayList.prototype.GetAt = function( index ) //Index must be a number {
    if( index > -1 && index < this.aList.length )
        return this.aList[index];
    else
        return undefined; //Out of bound array, return undefined
}
        
ArrayList.prototype.Clear = function() {
    this.aList = [];
}
 
ArrayList.prototype.RemoveAt = function ( index ) // index must be a number {
    var m_count = this.aList.length;
            
    if ( m_count > 0 && index > -1 && index < this.aList.length ) 
    {
        switch( index )
        {
            case 0:
                this.aList.shift();
                break;
            case m_count - 1:
                this.aList.pop();
                break;
            default:
                var head   = this.aList.slice( 0, index );
                var tail   = this.aList.slice( index + 1 );
                this.aList = head.concat( tail );
                break;
        }
    }
}
 
ArrayList.prototype.Insert = function ( object, index ) {
    var m_count       = this.aList.length;
    var m_returnValue = -1;
                
    if ( index > -1 && index <= m_count ) 
    {
        switch(index)
        {
            case 0:
                this.aList.unshift(object);
                m_returnValue = 0;
                break;
            case m_count:
                this.aList.push(object);
                m_returnValue = m_count;
                break;
            default:
                var head      = this.aList.slice(0, index - 1);
                var tail      = this.aList.slice(index);
                this.aList    = this.aList.concat(tail.unshift(object));
                m_returnValue = index;
                break;
        }
    }
                
    return m_returnValue;
}
 
ArrayList.prototype.IndexOf = function( object, startIndex ) {
    var m_count       = this.aList.length;
    var m_returnValue = - 1;
                
    if ( startIndex > -1 && startIndex < m_count ) 
    {
        var i = startIndex;
                    
        while( i < m_count )
        {
            if ( this.aList[i] == object )
            {
                m_returnValue = i;
                break;
            }
                        
            i++;
        }
    }
                
    return m_returnValue;
}
        
        
ArrayList.prototype.LastIndexOf = function( object, startIndex ) {
    var m_count       = this.aList.length;
    var m_returnValue = - 1;
                
    if ( startIndex > -1 && startIndex < m_count ) 
    {
        var i = m_count - 1;
                    
        while( i >= startIndex )
        {
            if ( this.aList[i] == object )
            {
                m_returnValue = i;
                break;
            }
                        
            i--;
        }
    }
                
    return m_returnValue;
}

 

Enjoy!  Tweet me if you have any questions @dannygnj

02.18.2010 07:07 by Danny Gershman | Comments (0) | Permalink