Blog.

Lightweight Theming

theme-colors

Users love when they can change the appearance of an application. I remember times when I’d spent hours browsing the internet in an attempt to find a Firefox theme that I would like. One was too dark another was too light, other had to big or too small fonts, yet another was not pixel perfect – I mean here and there was a pixel wide misalignment. After some hours I have returned back to the default theme that I somehow started to like after seeing all that other color and gradient combinations, different font styles and sizes and icons of varying quality and esthetics. Default theme, shipped with Firefox now appeared nicest.

Theming is not so beloved by programmers; myself included. We need to create different CSS, set colors, fonts, sizes into a visually pleasing package, create icons, combine them into sprites and finally tune all this to be aligned in one pixel accuracy. It is just too much work to do it once, not talking about five to ten themes that users would love to have.

Henry-Ford-with-Ford-Model-T-photo

I am sometimes even prone to use the approach of Henry Ford who gave customers of his famous Fort T these color options: “Any customer can have a car painted any color that he wants so long as it is black.” (Henry Ford, 1909)

Not only prone, more often than not I do deliver mono-thematic apps. The main reason being the amount of work as already mentioned. The complete theming as recommended by Sencha involves:

  1. generating a custom theme with Sencha Cmd sencha generate theme my-theme
  2. creating and editing many files in my-theme’s sass/var and sass/src directories
  3. editing the app’s app.json to use my-theme
  4. building the app with sencha app build
  5. deploying the build to the production server – this involves files and folders renaming as we want to use more than one theme, right?
  6. repeat points 1-6 for each theme
  7. and I must not forget; we need a UI to switch themes. Switching is equivalent to loading a different app, and de facto it is a different app built in point 4.

_JS09257-2kAre you still very enthusiastic to create 5-10 themes if the customer requires them? I’m not, thanks. Let’s rather persuade him that Ford’s approach is best: cheapest, fewest bugs, fastest.

So when all the theming hassle does make sense? When we cannot persuade the customer otherwise. Kidding.

Truly, I would create additional themes only if they were reusable the same way as Ext default themes are. (Or when the customer firmly insists.) Imagine you create one beautiful theme, and then you use it in all your projects. That’s worth the effort.

The theme switching still requires the app reload.

If you want to see the full theme switching at work go to Ext JS Kitchen Sink Examples and switch the theme from the upper-right corner menu.

The lightweight approach

_S503807-2kSometimes we or customers do not need or want an entirely new, unique and different looking theme. We create a couple of views with one of the Ext default themes, show them to the customer as a concept proof, and he says:
“All fine, works and we can go ahead with the concept, I only don’t like this blue. Can you make it darker? A sort of dark navy? Or dark magenta. Best if user could choose.”

That changes the game, doesn’t it? Everything but color is accepted. Font family and size are correct, header and border sizes and widths are fine, buttons look good as to shape and gradients, icons will do, no need to change the appearance of form fields, windows or message boxes. Everything’s fine, but color. That simplifies the matter significantly.

Neptune Theme Color Variations Example gives look and feel of this approach.

In pre-sencha-cmd times, when the support of an older Internet Explorer was needed, even coloring a theme was a daunting task because all rounded corners of buttons and windows, all gradients and other decorations were implemented as background images grouped together in sprites.

spritesImagine opening a black&white template sprite in Photoshop, color it with proper colors and gradients, save it in a different directory and repeat the process 20-30 times until we have all new background images? Too much.

At that time, good looking color variations of default Ext themes were very rare and were sold as not very cheap products – prices justified by the needed effort though.

It is much easier nowadays although older IE still needs images, but these images are generated as part of the application or theme build process. Sencha Cmd renders a sample page in a headless WebKit browser. The browser is called headless because it does not have a monitor output, but it is otherwise fully functional. The sample page contains all Ext components such as panels, tabs, buttons, and others with the current theme applied.

Having the sample page “displayed”, Sencha Cmd snaps “screenshot” and slices the resulting image to produce background images for Internet Explorer – that is why the process is called “slicing”.

Slicing is probably the most time consuming part of the application build, and you only need it if you intend to, or must support older versions of IE that do not support HTML5 and CSS3. Otherwise, you can skip this step and save the building time by adding skip.slice=true to sencha.cfg.

Creating themes

Back to the topic. Now, the only thing we need to change is so-called $base-color of the theme(s). $base-color is a SASS variable, and it contains, as its name suggests, the color value from which all other theme colors are derived. If the value is say ‘red’ then a gradient is calculated by lightening and darkening the basic red.

Still, we need to create one theme per color, each inheriting from same Ext theme – Neptune is a good candidate for extending, or Classic. The generation itself is easy. Run

sencha generate theme --extend ext-theme-neptune ext-theme-neptune-red

in the application directory. The command creates new folder in ../packages named after your theme, i.e. ../packages/ext-theme-neptune-red if the application we generate the theme in is part of a workspace. If it is a standalone application, not in a workspace, packages/ext-theme-neptune-red is created directly in the application folder, not one level up.

Now we need to change the base color to red. Values of SASS variables are defined in sass/var directory in files named after Ext components. For example, we can find variables for grid panel in sass/var/grid/Panel.scss Most general variables, including $base-color, are defined in sass/var/Component.scss. Obviously, if all components inherit from Ext.Component then the corresponding SASS file would hold variables that influence theming of all components.

component-scssSo we create packages/ext-theme-neptune-red/var/Component.scss and put $base-color: #c00000 !default; – or other shade of red if you will. Note: !default modifier says that the base color can be overridden in a more specific file.

The last step would be to build the theme with sencha package build. This is needed because we’re not going to follow the standard course of devel, include this theme in the app.json and rebuild the app. We’re going for dynamic runtime theme switching. Result of build is content of build subdirectory that contains CSS files and images, all we need to deploy to the production server. More about (automatic) deployment later.

Of course, we need to create and build a theme for each color variation we want to implement.

Theme switching

ext-theme-neptune-red-all-cssThe application build process compiles both application (app.js) and CSS (MyApp-all.css). To switch theme we inject additional stylesheet link in the document head, after MyApp-all.css, pointing to ext-theme-neptune-[color]-all.css. Because it comes after the default theme, the rules in it override defaults, and the app changes its color.

We remove this additional stylesheet from the head to switch back to the default theme the application has been compiled with.

Theme switching code:

,onThemeChange:function(combo) {
    var  theme = combo.getValue()
        ,href = '../packages/ext-theme-neptune-{0}/build/resources/ext-theme-neptune-{0}-all.css'
        ,link = Ext.fly('theme')
    ;

    if(!link) {
        link = Ext.getHead().appendChild({
             tag:'link'
            ,id:'theme'
            ,rel:'stylesheet'
            ,href:''
        });
    }

    if('default' !== theme) {
        link.set({href:Ext.String.format(href, theme)});
    }
    else {
        link.set({href:''});
    }

} // onThemeChange

Visit Neptune Theme Color Variations Example to see the theme switching in action.

Cons and pros

The CSS of the color variations and resources are not optimized hence larger than necessary. The standard build process does not include styles of components that are not used by the application. If the application does not use, let’s say, trees the styles and resources for trees are not included in the build. Our color themes are independent of the applications using them, so they are complete -all.css. This can make a difference in CSS files about 100 – 200 kB.

We also need to load CSS “twice” – first comes normal application CSS file the theme color variation file.

I don’t think that the above could mean a big issue with normal internet speed and properly setup caching – game would be different in the mobile world, however.

Big pro is that we do not need to reload the application on theme change, but we can switch mid-form filling. With the standard approach, we should need to sense if there are any unsaved changes an only allow theme switching if not, or if user agrees the changes will be lost on a reload.

Automation

The color theme creation, build and deployment is not very difficult, and it is not too much work to do it manually. After all, color themes do not need updates, so we do not need to rebuild them during the normal course of the application development. The initial effort you put into creating the theme packages, files and building them does not need to be repeated hence we don’t need to automate the process.

There is some space for automation, however. Imagine you have a couple of color variations of an Ext theme nicely built as packages in your workspace. Now, every time you create an application that uses them you also need to:

  1. implement a theme switcher (combo, menu, …)
  2. deploy (copy) theme files to the production server

It is very wise to write theme switcher, whether it is a menu, combo or radios as an extension that can be the reusable in all your projects. I’m thinking of writing combo and menu and make them available for you to save time.

Deployment process can be automated by creating new targets in build.xml of the applications. Then you could call Sencha Cmd to take care of copying the files. For example, sencha ant color-black could copy theme builds to the build folder from where you normally deploy the app to production, or the command could even build the theme packages.

Nevertheless, it is questionable if to invest too much time into theme build/deploy process as it needs to be done only once per application development cycle. (Well, if we upgrade Ext we also need to rebuild and re-deploy our color theme packages, but it is quite rare.)

Here is how we could do it:

    <!-- all colors -->
    <target name="color-all" depends="init, color-black,color-gray,color-green,color-orange,color-red" />

    <!-- black -->
    <target name="color-black" depends="init">
        <local name="color" />
        <property name="color" value="black" />
        <echo file="/dev/stdout">Building ${color} color
</echo>
        <exec executable="sencha"
              dir="../packages/ext-theme-neptune-${color}"
              failifexecutionfails="false">
            <arg value="package"/>
            <arg value="build"/>
            <arg value="--clean"/>
        </exec>

        <copy todir="${build.out.base.path}/../packages/ext-theme-neptune-${color}/build">
            <fileset dir="${app.dir}/../packages/ext-theme-neptune-${color}/build" includes="**/*"/>
        </copy>
    </target>

    <!-- gray -->
    <target name="color-gray" depends="init">
        <local name="color" />
        <property name="color" value="gray" />
        <echo file="/dev/stdout">Building ${color} color
</echo>
        <exec executable="sencha"
              dir="../packages/ext-theme-neptune-${color}"
              failifexecutionfails="false">
            <arg value="package"/>
            <arg value="build"/>
            <arg value="--clean"/>
        </exec>

        <copy todir="${build.out.base.path}/../packages/ext-theme-neptune-${color}/build">
            <fileset dir="${app.dir}/../packages/ext-theme-neptune-${color}/build" includes="**/*"/>
        </copy>
    </target>

    <!-- green -->
    <target name="color-green" depends="init">
        <local name="color" />
        <property name="color" value="green" />
        <echo file="/dev/stdout">Building ${color} color
</echo>
        <exec executable="sencha"
              dir="../packages/ext-theme-neptune-${color}"
              failifexecutionfails="false">
            <arg value="package"/>
            <arg value="build"/>
            <arg value="--clean"/>
        </exec>

        <copy todir="${build.out.base.path}/../packages/ext-theme-neptune-${color}/build">
            <fileset dir="${app.dir}/../packages/ext-theme-neptune-${color}/build" includes="**/*"/>
        </copy>
    </target>

    <!-- orange -->
    <target name="color-orange" depends="init">
        <local name="color" />
        <property name="color" value="orange" />
        <echo file="/dev/stdout">Building ${color} color
</echo>
        <exec executable="sencha"
              dir="../packages/ext-theme-neptune-${color}"
              failifexecutionfails="false">
            <arg value="package"/>
            <arg value="build"/>
            <arg value="--clean"/>
        </exec>

        <copy todir="${build.out.base.path}/../packages/ext-theme-neptune-${color}/build">
            <fileset dir="${app.dir}/../packages/ext-theme-neptune-${color}/build" includes="**/*"/>
        </copy>
    </target>

    <!-- red -->
    <target name="color-red" depends="init">
        <local name="color" />
        <property name="color" value="red" />
        <echo file="/dev/stdout">Building ${color} color
</echo>
        <exec executable="sencha"
              dir="../packages/ext-theme-neptune-${color}"
              failifexecutionfails="false">
            <arg value="package"/>
            <arg value="build"/>
            <arg value="--clean"/>
        </exec>

        <copy todir="${build.out.base.path}/../packages/ext-theme-neptune-${color}/build">
            <fileset dir="${app.dir}/../packages/ext-theme-neptune-${color}/build" includes="**/*"/>
        </copy>
    </target>

Disclaimer: I am far from being an ant expert so the above implementation is most likely clumsy, suboptimal and could be coded much better.

The above code also builds the theme packages – feel free to remove the building code and keep the copying code, if you will.

saki
Follow me:
Latest posts by saki (see all)

4 Responses

  1. Hello Saki,

    This is a good starting point. One question though: should the above build.xml file replace the one in extjs app or it’s something different.

    Thanks.

      1. Hello Saki,

        I am having some css compile issues when i change the location of the ext framework
        I really need your expert advise. please take a look at following questions that i posted.
        Thanks in Advance!!

        https://www.sencha.com/forum/showthread.php?300693-Help!!-Sencha-Cmd-5.1-error-on-updating-Extjs-framework-relative-absolute-path&p=1098688#post1098688

        http://stackoverflow.com/questions/29973370/sencha-cmd-5-1-error-on-updating-extjs-framework-relative-absolute-path

        -Harry

        1. Sencha Cmd is “used to some standards” so if you want anything non-standard you need to re-configure, change something and, if it becomes too difficult or takes too long, you may reconsider if you really need the non-standard setup, if it is worth the effort.

          Unfortunately, I do not have a one-liner solution for you because I’ve never needed to keep Ext out of workspace – well, the downloaded version is out the workspace but the runtime copy is inside.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Enter your username and password to log into your account. Don't have an account? Sign up.

Want to collaborate on an upcoming project?