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.
<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.
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.
.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.