Branding is one of the more important aspects of business to business application development. In specific, nothing is more powerful then walking into a business meeting with a client whom you are trying to sell you application to; then showing them a completely redesigned application -- specifically the way they would want it to look (their assets, logos, color pallete, etc). Whether its a mobile app (iOS, Android, whatever), or desktop application -- personalizing an application can make the difference between landing a deal versus having the client walk out the door. It makes it "their" experience rather than your own.
While branding isn't necessarily used and/or useful for every application -- I believe a great deal of applications can benefit from basic branding. After all, "branding" is just another way of saying "personalization"...and who doesn't like to personalize an application to their tastes. That being said -- with branding an application comes architectural challenges -- however, with the right tools its as simple as 1...2...waffle.
This article is going to use some tools in specific in order to accomplish the branding goals. This article only focuses on the use of three tools to accomplish branding in a very simple, logical way. Im sure there are a ton of different tools / mechanisms to brand an application -- but I find this the easiest one i've seen so far.
- AngularJS (https://angularjs.org/)
- ngMaterial (https://material.angularjs.org/)
- (Optional for mobile) Ionic Framework (http://ionicframework.com/)
The assumption here is that you know how to set up an angular application, manage dependencies and do all that jazz. If you are not familiar with this -- then I suggest you take a look at the guides available on the different sites.
The basis for running an application in angular through a branding configuration is pretty simple and follows only a few steps,
- Inherit a data structure which is parsible by the application (most likely JSON), and expose this to the different parts of the application in order to use it throughout.
- Use the md-theme property in ngMaterial to manipulate the branding of the different components of the application
- Use the md-theme attribute on different custom elements to implement personalized branding
Seems pretty simple & logical right? So what the heck does this look like?
// The below is an example of a static configuration js file which can be
// included before your regular application. Since this registering a
// variable in the global space, we need to inject this into the angular
// application (see below).
// make sure that we have the application object available
if (!angular.isDefined(application)) {
var application = {};
}
// setup a base object that can be inherited
application.config = {};
application.config.branding = {};
application.config.branding.palette = {};
// branding settings (images)
application.config.branding.icon = "../path/to/icon";
application.config.branding.background = "../path/to/background";
// branding pallette (colors)
application.config.branding.palette.primary = "5E7585";
application.config.branding.palette.secondary = "438BD9";
application.config.branding.palette.success = "71BC4B";
application.config.branding.palette.failure = "D80000";
Pretty simple, eh? Nothing too special...just a static file which can be shared/injected into the application space. Now how does this get exposed to the AngularJS application, and furthermore how does the application ingest the global object and configure the application accordingly?
// angular.constants.js
(function() {
'use strict';
angular
.module('[applicationModuleName]')
.constant('app', application);
})();
// angular.js
(function() {
'use strict';
angular
.module('[applicationModuleName]', [])
.config([
'app',
'$mdThemingProvider'
Config
])
.run([
'app',
'$rootScope',
Bootstrap
]);
/**
* @description
* This function will go ahead configure the application, and setup the
* application in whatever environment is needed.
*
* @param {application} app
* @param {md.$mdThemingProvider} $mdThemingProvider
*/
function Config(app, $mdThemingProvider) {
// Make sure that we are fed a config array, otherwise return a no-op
// so nothing happens.
if (!angular.isDefined(application.config.branding.palette)) {
return;
}
// Load in the major theme palletes
for (var themeName in config.theme.palette) {
var themeColor = config.theme.palette[themeName];
$mdThemingProvider
.definePalette(themeName, {
'50' : lighten('#'+themeColor, 50),
'100' : lighten('#'+themeColor, 40),
'200' : lighten('#'+themeColor, 30),
'300' : lighten('#'+themeColor, 20),
'400' : lighten('#'+themeColor, 10),
'500' : themeColor,
'600' : darken('#'+themeColor, 10),
'700' : darken('#'+themeColor, 20),
'800' : darken('#'+themeColor, 30),
'900' : darken('#'+themeColor, 40),
'A50' : lighten('#'+themeColor, 50),
'A100' : lighten('#'+themeColor, 40),
'A200' : lighten('#'+themeColor, 30),
'A300' : lighten('#'+themeColor, 20),
'A400' : lighten('#'+themeColor, 10),
'A500' : themeColor,
'A600' : darken('#'+themeColor, 10),
'A700' : darken('#'+themeColor, 20),
'A800' : darken('#'+themeColor, 30),
'A900' : darken('#'+themeColor, 40),
'contrastDefaultColor': 'light',
'contrastDarkColors': [
'50', '100', '200', '300', '400', 'A100'
]
});
// Setup the theme
$mdThemingProvider.theme(themeName).primaryPalette(themeName);
}
/**
* @description
* This function will returned a color code based on the color passed and
* the percentage you want it darkened.
*
* @param {string} hex
* @param {number} percent
*
* @returns {string}
*/
function darken(hex, percent) {
if (parseInt(percent) < 0) {
percent = 0;
}
return manipulateColor(hex, -1 * percent);
};
/**
* @description
* This function will returned a color code based on the color passed and
* the percentage you want it lightened.
*
* @param {string} hex
* @param {number} percent
*
* @returns {string}
*/
function lighten(hex, percent) {
if (parseInt(percent) < 0) {
percent = 0;
}
return manipulateColor(hex, percent);
};
/**
* @description
* This function will manipulate a color code based on the color passed and
* the percentage you want it lightened.
*
* @param {string} hex
* @param {number} percent
*
* @returns {string}
*/
function manipulateColor(hex, percent) {
var R = parseInt(hex.substring(1,3),16);
var G = parseInt(hex.substring(3,5),16);
var B = parseInt(hex.substring(5,7),16);
R = parseInt(R * (100 + percent) / 100);
G = parseInt(G * (100 + percent) / 100);
B = parseInt(B * (100 + percent) / 100);
R = (R<255)?R:255;
G = (G<255)?G:255;
B = (B<255)?B:255;
var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));
return "#"+RR+GG+BB;
}
}
/**
* @description
* This function will bootstrap the application, and kick off any processes
* which are needed to be run before initial execution. This may include
* things like OAuth token checks, etc.
*
* @param {application} app
* @param {angular.$rootScope} $rootScope
*/
function Bootstrap(app, $rootScope) {
// expose the application branding to ALL views so that it can be
// used throughout the application.
$rootScope.branding = app.config.branding;
// ..do some other stuff here
}
})();
<!DOCTYPE html>
<html>
<head>
<!-- ... meta crap -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<!-- ... ze title -->
<title></title>
<!-- ... ionic stuff (if ionic) -->
<link href="vendor/ionic/css/ionic.css" rel="stylesheet">
<script src="vendor/ionic/js/ionic.bundle.js"></script>
<!-- ... cordova stuff (if ionic) -->
<script src="vendor/ngCordova/dist/ng-cordova.js"></script>
<script src="cordova.js"></script>
<!-- ... application css files -->
<link href="src/includes.css" rel="stylesheet" />
<link href="src/application.css" rel="stylesheet" />
</head>
<body ng-app="[applicationModuleName]">
<!-- ... some html here -->
<!-- ... vendor includes (angular, jquery, whatever) -->
<!-- ... application config file -->
<script src="src/application.config.js"></script>
<!-- ... application js files -->
<script src="src/application.js"></script>
</body>
</html>
So the above pretty much sums up everything you need in order to brand an application. Not so difficult right? Theres a couple of gotchas here that I will need to explain --
- You will notice that I am using 4 colors (primary, secondary, success, failure). This can be extended to however many you want. It does not matter. 10....15....9999999999, pick your poison. The only catch is that they will need unique names so you can reference them in the html.
- For each palette color I am generating the hues & accents. This is a huge timesaver. You can easily see how you could set your own through the config, but I am lazy and want to just figure it out automagically. Hence, the lighten & darken utility functions are put to major use.
- Configuration does not have to be done through a global variable. This is just the simplest example -- you will need to figure out whats best for your application (regardless the implementation is still the same):
- Does the config need to be data driven so that it can be managed from a database?
- Do I need extensible configs on the fly?
So with that all said and done, what do we have?
- A config driven application
- A generic branding palette that can be expanded upon
- A branding variable exposed to all views through the $rootScope
So how the heck do we use it at this point?
You're going to laugh at how easy this is with ngMaterial. It literally is like shooting fish in a barrell:
<!-- ... theme on a button -->
<md-button md-theme="[definedThemeName]" class="md-raised md-primary">...</md-button>
<!-- ... theme on a card -->
<md-card md-theme="[definedThemeName]">
<md-card-content>
</md-card-content>
</md-card>
<!-- ... branding on an image -->
<img ng-src="{{ branding.icon }}" />
The [definedThemeName] is literally the name of the palette you have defined (refer back to the configuration example above). In our case it can be one of the following,
- primary
- secondary
- success
- failure
This is the basic use cases for how we develop ngMaterial driven applications with our custom branding.
There are various ways to extend the ngMaterial "mindset" into our own application -- and one of them is through the use of custom directives. The idea here is that we create our own custom directives for whatever elements we want to "brand". For example, here is a custom text element (wraps a p tag):
(function() {
'use strict';
/**
* @ngdoc module
* @name application.directive.element.text
*
* @description
* The module declaration for the text directive.
*/
angular
.module('application.directive.element.text', [
])
.directive('appText', [
TextDirective
]);
/**
* @ngdoc directive
* @name appText
* @module application.directive.element.text
*
* @restrict E
*/
function TextDirective() {
// Expose the directive
return {
restrict: 'E',
templateUrl: [
'<p>',
'<ng-transclude></ng-transclude>',
'</p>'
].join(''),
transclude: true,
replace: false,
link: link
};
/**
* @description
* This function will set up the dom manipulation, and other action events
* necessary for this directive.
*
* @param {!angular.$scope} $scope
* @param {!angular.$element} $element
* @param {!angular.$attr} $attr
*/
function link($scope, $element, $attr) {
if (!angular.isDefined($attr.color)) {
return;
}
// Using the color attribute, set the css properties accordingly
$element.css({ color: '#'+$attr.color };
};
}
})();
So what is the point of creating custom elements?
- It allows you to extend your brand onto the element using attributes (try not to pollute the scope with branding crap, it just becomes more difficult to manage).
- You can now style based on the "tag name" of the element (i.e. app-text is a selector in css now)
The amazing thing about this whole process is that it works out of the gate for Ionic. In the examples section youll see that I included the html/scripts/css needed in order to make the index page work for the ionic framework. The downside to this is that the default ionic element will not inherit the md-theme properties, or any theming for that matter....you will have to roll your own with custom elements (not too difficult anyhow).
Regardless of the situation, I really recommend looking into using ng-material for interface elements on hybrid apps using ionic. Not that the Ionic team hasn't done a good job -- its quite the opposite, but the ng-material stuff is just that much easier, runs on flexbox, and is just wonderful to use. (still kudos to ionic).
- This may not be the best way of doing things. It is more of an open discussion in terms of the best way to brand an application. The one thing I do know -- this works....and it works REALLY well. I use it for a couple of production applications with Ionic builds and it is lightning fast to change out an applications branding on the fly.
- Loading in a config through ajax is tricky since you cannot put in the $http service (or any service) in the .config portion of the angular application. If someone knows a way around this im all ears.
- Use this example at your own risk -- it is a good template, but you should be willing to spend some time and use this as a "concept" rather than the defacto way of doing things for your own application.
- This whole concept ONLY works with ngMaterial due to the way md-theme is implemented. If you want to roll your own, you probably will have to create a set of 100% custom elements, or roll your own themeing into other frameworks (bootstrap, ionic whatever)
I hope you enjoyed this little article...im sure I have missed some stuff but wanted to get this out before it was an afterthought since I think it is so valuable in creating a great user experience. If anyone has any questions please feel free to email me directly! Also, if anyone has feedback, leave a comment and I'll work it into this running document.
Cheers, Ryan
@ryanpager
Loading in a config through ajax can be done by manual bootstrap app after fetching config.
If you have jQuery or you can use angular provider to get $http service before bootstrapping app.