Monday, April 16, 2012

Preventing a CSS Jungle

The problem using CSS in a large project is that over time the amount of CSS grows. When a rule is defined generically, like .shadedData, then it is difficult to know every place it is used. And when a developer doesn't know where it is used, modifying the style is done by adding a new rule .shadedData2. Even rules that aren't as generic as this can suffer the same fate. A CSS garden quickly becomes a jungle.

I recently had to add support for multiple themes to a product. The product is going to be OEM'd and needs to represent different companies' brands. The product had a lot of CSS that had become overgrown. In this post, I am going to describe the changes I made to the CSS infrastructure to support multiple themes and to also help the development team better maintain the CSS rules. And even if a project only needs a single theme, I think the following infrastructure is still beneficial to a project.

File Structure
themes
  common
    images
    layout
      header.css
    pages
      account.css
    widgets
      dialog.css
      button.css
    common.css

  oemA
    images
    layout
      header.css
    pages
      account.css
    widgets
      dialog.css
      button.css
    oemA.css

  oemB
    images
    layout
      header.css
    pages
      account.css      
    widgets
      dialog.css
      button.css
    oemB.css

The common theme contains general layout rules that will be used for all themes. There are no hard and fast rules for what belongs in the common theme, but it should be anything that should be used in all OEM themes, including heights, widths, paddings, and margins. Generally the common theme wouldn't contain any color definitions. However, with the work I just recently completed, there was part of the application that used a grey color and did not change based on the OEM, so I put it in the common theme. Again, this can be more of an art and you should do what feels right for you. You can always move things later.

The oemA and oemB directories contain css rules for particular OEMs. Only one of these themes should be used at a time in combination with the common theme. The css rules in these files are specific to the OEM and are generally the color scheme. The product I was recently working on is a business application and we weren't looking to make any major changes to the UI. The colors and in some cases the fonts were changed.

This is a good place to talk about CSS preprocessors. SASS and LESS are two examples of such preprocessors. Preprocessors allow you to define variables (colors, borders, etc) and then use the variables throughout the stylesheets. At first glance, it would appear that these preprocessors would eliminate the need to have oemA and oemB defined. One could just have the a single set of CSS rules defined and apply a different set of variables for each OEM. However, it was my experience that the colors don't translate one for one across themes. For example, in the original theme there were tabs that used the primary color of the theme. When implementing an additional theme, the tabs looked better using one of the secondary colors. It is my opinion that the CSS preprocessor provides value when used within a theme, but should not be used to provide variables across themes.

In the file structure above, the images directory contains images for the particular theme. I don't believe it is a good practice to reference images in a different theme. However, one exception is referencing images in the common theme. The common theme could contain a set of icons and for a particular OEM theme and you might want to override an image with one of those icons. Again, there are no hard and fast rules; this is an art.

The layout directory contains files that define CSS rules for the framework of the web application. These include things like headers, footers and sidebars. The pages directory is for specific page layouts within the application. The widgets directory is for widgets that are used throughout the application. Buttons and dialogs are two examples of such widgets.

The CSS
.themeName .pageOrWidget .theSpecificCssRule

What makes this concept work is nesting CSS selectors. Each CSS rule declaration should begin with the theme that it belongs to. The theme name should be added to the CSS of the body element and by doing so, you now control whether or not a theme is applied to the page. This is how Dojo applies themes.

Each CSS rule declaration should further restrict its use by specifying the page or widget that it is being used for as the second CSS selector. Each combination of the first two CSS selectors should equate to its own file in the file structure above. Any CSS selectors after the first two are specific to the need of the rule.

The Account Page Example
<body class="common oemB">
  <div class="logo"></div>
  <div class="accountPage">
    <div class="button">
      <span class="buttonText">OK</span>
    </div>
    <div class="accountSummary">...</div>
  </div>
</body>
/* common/widgets/button.css */
.common .button .buttonText {
 font-size: 1.25em;
}

/* oemA/widgets/button.css */
.oemA .button .buttonText {
 color: red;
}

/* oemB/widgets/button.css */
.oemB .button .buttonText {
 color: green;
}

Looking at this example, I would expect the logo to be defined in header.css, the button and buttonText to be defined in the button.css and accountSummary in the account.css

Compressing CSS

While defining CSS rules across multiple files is good for maintenance, it's horrible for production. So as part of the build process, the CSS files should be compressed and combined into fewer. I generally make one file per theme and the server side code that emits the html knows what stylesheets to include and what css classes to add to the body element.

The General CSS Declaration
.color { color: red; }

<div class="button">
    <span class="red">OK</span>
</div>

Avoid the general approach. What happens if we want to make the color blue? We have to modify the code that creates the widget to apply the blue class. What about multiple themes that each use a distinct color? The widget now needs to have knowledge of the current theme. The purpose of CSS is to take this responsibility away from the widget code. By using the general approach, the widget is again responsible for specific styling logic.

The widget code should be thought of as defining the theme "interface" using CSS classes. The CSS rules are the implementations of the "interface". The class names that make up the "interface" should describe their purpose (.buttonText) but not a specific implementation (.red).

CSS Grouping
.oemA .button .buttonText,
.oemA .button .buttonText.important,
.oemA .anotherWidget .widgetText {
 color: red;
}

CSS grouping provides the ability to not repeat yourself. While we as developers always try to adhere to the DRY principle, it has been my experience that overusing CSS grouping leads to poor maintainability.

Using the example above, where would the CSS rule reside in the file structure that we previously defined? You could make a common file beneath the widgets directory for rules like this. The problem with this approach is that over time a lot of widget rules would migrate to the common file and when you need to make modifications to a widget, it will require you to look in multiple files. Worse yet, if this rule lives in the anotherWidget file, looking for all button CSS now requires you to look throughout the jungle, thus defeating the purpose of having a separate file per widget.

/* oemA/widgets/button.css */
.oemA .button .buttonText,
.oemA .button .buttonText.important {
 color: @red; /* @red is less syntax */
}

/* oemA/widgets/anotherWidget .css */
.oemA .anotherWidget .widgetText {
 color: @red; /* @red is less syntax */
}

I think a better approach is to limit the use of CSS grouping to rules that belong in a single file and use a CSS preprocessor to provide common definition across multiple files within a single theme. This allows for the definition of red to be defined once and allows the CSS rules for a specific widget to live in a single file.

Wrapping Up

I have mentioned this a couple times throughout the post, but it bears repeating - using CSS is very much an art. I have tried to define a set of best practices around CSS that allows CSS to do what it was designed to do and still be maintainable across a large project that over time will inevitably have to be modified and even refactored.

Wednesday, April 11, 2012

A JSON Serialization Engine - Part II


In part I, I introduced the JsonStoreEngine and described how the developer uses the engine to serialize Java objects into JSON and can customize how the engine serializes certain objects. In this post, I am going to show more of the inner workings of the JsonStoreEngine.

Under the hood, the JsonStoreEngine uses Jackson for the JSON serialization. The JsonStoreEngine provides the logic to emit the flat list of items using the reference object notation to represent object references.

A Simple Example
public class OrderStatus {
    
    public String getIdentifier();
    
    public String getName();
    
    public String getDisplayOrder();
}

public class OrderStatusJsonConverter 
 extends AbstractTypedJsonConverter<OrderStatus> {

    public String determineIdentifier(OrderStatus obj) {
        return String.format("orderStatus::%s", obj.getIdentifier()); 
    }
    
    protected String onDetermineType(OrderStatus obj);
        return "orderStatus";
    }
    
    protected void onWriteObjectProperties(
      JsonSerializationContext context, OrderStatus obj) 
      throws IOException;
      
        context.writeProperty("name", obj.getName());
        context.writeProperty("order", obj.getDisplayOrder());
    }
}
The above example is the most basic. An OrderStatus object has no references to other objects and the engine serializes the primitive properties. This example produces the following output:
[{ id: 'orderStatus::1', _type: 'orderStatus', 
    name: 'Pending', order: 10 
}]

A Bigger Example
Here is an example where the engine serializes object references.
public class Order {
    
    public String getIdentifier();

    public String getOrderNumber();
    
    public Customer getCustomer();
    
    public OrderStatus getOrderStatus();

    public List getOrderLineItems();
}

public class OrderJsonConverter 
 extends AbstractTypedJsonConverter<Order> {

    public String determineIdentifier(Order obj) {
        return String.format("order::%s", obj.getIdentifier()); 
    }
    
    protected String onDetermineType(Order obj);
        return "order";
    }
    
    protected void onWriteObjectProperties(
      JsonSerializationContext context, Order obj) 
      throws IOException;
      
        context.writeProperty("orderNumber", obj.getOrderNumber());
        context.writeProperty("customer", obj.getCustomer());
        context.writeProperty("orderStatus", obj.getOrderStatus());
        context.writeProperty("lineItems", obj.getOrderLineItems());
    }
}
And the output produced:
[{ id: 'order::1', _type: 'order', orderNumber: 1234, 
    customer: { _reference: 'customer::1' }, 
    orderStatus: { _reference: 'orderStatus::1' },
    lineItems:[
        { _reference: 'orderLine::1' }, 
        { _reference: 'orderLine::2' }
    ]
},
{ id: 'customer::1', _type: 'customer', 
    name: 'Anderson, Jose'
},
{ id: 'orderStatus::1', _type: 'orderStatus', 
    name: 'Pending', order: 10 
},
{ id: 'orderLine::1', _type: 'orderLine', 
    order: { _reference: 'order::1' }, 
    price: 1.07, quantity: 10, 
    product: { _reference: 'product::1' } 
},
{ id: 'orderLine::2', _type: 'orderLine', 
    order: { _reference: 'order::1' }, 
    price: 4.48, quantity: 1, 
    product: { _reference: 'product::2' } 
}]

Where the Engine Does the Work
context.writeProperty("orderStatus", obj.getOrderStatus());
When the converter asks to write the order status, the engine intercepts and emits the reference object. The engine then registers the object for deferred serialization, only if the object hasn't already been serialized or registered for deferred serialization. Once the serialization of the original object is complete, the engine then serializes the objects that had been deferred.

Using the JsonStoreEngine and the ViewModelStore, we now have a framework in place to ship a complex model of data that is represented by Java objects on the server to javascript objects that can be consumed on the web client and used as a view model. The complexity of the data model can include references to other objects, circular object references and self references and also gives us control to specify how certain objects are serialized.

This will be a core component to building a Dojo View to plug into Spring's Web MVC architecture. The code for the serialization engine is checked into the evince framework repository on GitHub. There is also some more documentation on how to customize the serialization and conversion process.

Monday, April 9, 2012

A JSON Serialization Engine - Part I


In this previous post, I describe a specific JSON format that I want to consume on the client side using Dojo. In this post, I am going to show how to take a Java object and create that JSON format using the JsonStoreEngine.

The JsonStoreEngine is something I have written, has a simple API and works as a black box. The developer passes a Java object into the engine and the engine gives a custom formatted JSON structure back. The JsonStoreEngine also provides flexibility, giving the developer the ability to customize and control how particular objects are converted to JSON.

This version of the JsonStoreEngine is one way. It does not support deserialization. My primary use case for this engine is to serialize an object graph into a Dojo store, so that I can use the data on the client side to build the web page. When communicating back with the server, I only send a limited set of data using the standard HTTP post mechanism.

The Data

This custom JSON format is flat list of items where references to other items use a special notation. The following is an example of that structure:
var dataArr = [
  { id: 'customer::1', _type: 'customer', 
    name: 'Anderson, Jose',
    address: { _reference: 'address::1' } 
  },
  { id: 'address::1', _type: 'address', 
    address1: '2272 Stratford Drive', 
    address2: 'Honolulu, HI 96814' 
  },
  { id: 'customer::2', _type: 'customer', 
    name: 'Gomez, Marcus',
    address: { _reference: 'address::2' } 
  },
  { id: 'address::2', _type: 'address', 
    address1: '2718 College Avenue', 
    address2: 'Dayton, OH 45459' 
  },

  // ... other customers ...

  { id: 'product::1', _type: 'product', 
    name: 'Ball Original Classic Pectin Small Batch', 
    price: 1.07 
  },
  { id: 'product::2', _type: 'product', 
    name: 'Ball 4-Pk 16 Oz. Wide Mouth Class Canning Jars with Lids', 
    price: 4.48 
  },

  // ... other products ...

  { id: 'orderStatus::1', _type: 'orderStatus', 
    name: 'Pending', displayOrder: 10 
  },
  { id: 'orderStatus::2', _type: 'orderStatus', 
    name: 'Back Ordered', displayOrder: 30 
  },
  { id: 'orderStatus::3', _type: 'orderStatus', 
    name: 'Shipped', displayOrder: 20 
  },
  { id: 'order::1', _type: 'order', orderNumber: 1234, 
    customer: { _reference: 'customer::1' }, 
    orderStatus: { _reference: 'orderStatus::1' },
    lineItems:[
        { _reference: 'orderLine::1' }, 
        { _reference: 'orderLine::2' }
    ]
  },
  { id: 'orderLine::1', _type: 'orderLine', 
    order: { _reference: 'order::1' }, 
    price: 1.07, quantity: 10, 
    product: { _reference: 'product::1' } 
  },
  { id: 'orderLine::2', _type: 'orderLine', 
    order: { _reference: 'order::1' }, 
    price: 4.48, quantity: 1, 
    product: { _reference: 'product::2' } 
  },

  // ... other orders ...
];
The API

The API consists of two methods. The developer can call serialize only passing the object to be serialized. The JSON will be returned as a String to the developer. Alternatively, the developer can also pass an OutputStream. Doing so will write the JSON to that stream and nothing will be returned.
public String serialize(Object model)

public void serialize(OutputStream out, Object model) 
    throws IOException
Customizing the Conversion

The default converter is the PojoConverter. In the example below, a second PojoConverter is registered to handle the OrderLine class. Now anytime an OrderLine object is serialized, this converter will be used. This converter is configured to only serialize two fields: product and price. So looking at the data example above, the order field of an OrderLine object would not be serialized.
JsonStoreEngine engine = new JsonStoreEngine();
  
PojoConverter productConverter = new PojoConverter();
productConverter.setIncludeFieldsByDefault(false);
productConverter.setOverriddenFields(
  new String[] { "product", "price" });
engine.getLookupMap().put(OrderLine.class, productConverter);
The developer can also create a custom implementation of the JsonConverter interface. This custom implementation can then be registered in the same manner as the as the PojoConverter above.

The code for the serialization engine is checked into the evince framework repository on GitHub. In part II, I will look into the black box and go over the details of how the engine works.