While developing web applications I have often needed to change the style of one or more elements based on the state of the data model used by the application. I have an inherent aversion to conditionals in views so went looking for other options. I came up with templated styles that works with both server and client side templating systems.

The code examples use Angular and can be found here

The example displays a traffic light which has 4 states
  • stop

  • prepare-to-go

  • go

  • prepare-to-stop

Some traffic light systems jump from stop to go and don’t use the prepare-to-go state. Adding this state for the example shows how differences between the view and data model could be handled.

There are 3 lights red, amber, green. Each light is either on or off based on the status but there are really three states - one for each light.

e.g. for Red

  • The status of the red light when the red light is lit

  • The status of the red light when the amber light is list.

  • The status of the red light when the green light is list.

This model was used in coming up with the styles to be applied.

Conditional View approach

Probably the most obvious approach is to use a conditional in the template based on the state:

          <div ng-switch on="TrafficLights.status">
            <div class="animate-switch" ng-switch-when="prepare-to-go|prepare-to-stop" ng-switch-when-separator="|">
              <div class="circle traffic-light-red-amber"></div>
              <div class="circle traffic-light-amber-amber"></div>
              <div class="circle traffic-light-green-amber"></div>
            </div>
            <div class="animate-switch" ng-switch-when="stop">
              <div class="circle traffic-light-red-red"></div>
              <div class="circle traffic-light-amber-red"></div>
              <div class="circle traffic-light-green-red"></div>
            </div>
            <div class="animate-switch" ng-switch-when="go">
              <div class="circle traffic-light-red-green"></div>
              <div class="circle traffic-light-amber-green"></div>
              <div class="circle traffic-light-green-green"></div>
            </div>
            <div class="animate-switch" ng-switch-default>
              <p>Unknown state<p>
            </div>
          </div>

There is no change to the model. The view interprets the state and represents that state as an appropriate view of the traffic light.

The code is pretty verbose. Adding traffic light states means changes to the view.

Templated Styles approach

This approach removes all the conditional code from the view and uses a new color view model attribute to control the style applied to each light.

Templated style traffic light
          <div>
            <div class="circle traffic-light-red-{{TrafficLights.color}}"></div>
            <div class="circle traffic-light-amber-{{TrafficLights.color}}"></div>
            <div class="circle traffic-light-green-{{TrafficLights.color}}"></div>
          </div>

Using template substitution for the element class removes the need for a conditional. But we have to update model to include a color attribute derived from the status.

First we need to change the controller attribute into a property so when the value changes we can recognize the change.

Property definition
    Object.defineProperty(trafficLight, 'status', {
      get: function () {
        return _status;
      },         
      set: function (value) {
        _status = value;
        updateColor(); (1)
      }
    });
1 Called each time the status is changed

The 'updateColor' function looks very similar to the template conditional

    function updateColor() {
      console.log("Update color called");
      switch (_status) {
      case "go":
        trafficLight.color = "green";
        break;
        
      case "stop":
        trafficLight.color = "red";
        break;
        
      default:
        trafficLight.color = "amber";
        break;
      }
    }

A key difference is that JavaScript is much easier to unit test.

The CSS
.circle {
   width: 50px;
   height: 50px;
   border-radius: 50%;
   display: inline-block;
   margin-right: 20px;
}

.traffic-light-red-red {
   background-color: #a02128;
}

.traffic-light-red-amber {
   background-color: gray;
}

.traffic-light-red-green {
   background-color: gray;
}

.traffic-light-amber-red {
   background-color: gray;
}

.traffic-light-amber-amber {
   background-color: #F7BA0B;
}

.traffic-light-amber-green {
   background-color: gray;
}

.traffic-light-green-red {
   background-color: gray;
}

.traffic-light-green-amber {
   background-color: gray;
}

.traffic-light-green-green {
   background-color: #317f43;
}
Wrapping up

By creating a view model with a view attribute for the color the view becomes simpler - zero code

Being able to test views is key differentiator. Unit tests are far faster than functional tests so feedback is faster. The view is logic free.

Each combination can be styled differently so the view model is more explicit.

As I mentioned at the beginning this technique can be applied with any server or client side templating systems that allow attributes to be templated.