sencha-logoTheming in ExtJS 4.2+ is a beautiful, tricky, complicated mistress. On the one hand, it has been completely reworked (for the better…mostly) to be a lot more extensible, take advantage of theme inheritance, care less about how themes are created (and where), and a host of other really nice features.

However, the elegance of the new theming engine comes with a price…namely, a steep learning curve (I should know, I’m still having trouble ascending this hill). Prior to 4.2, casual theming was really easy. You could open a single file, edit a few SASS variables, run the compiler, and you were done. Now, however, the logic and structure of themes is much more distributed, make for a lot more complexity than most people who dabbled in the previous version are probably prepared for.

And of course, there is the brand-newness of it. The documentation, while helpful in getting setup, is particularly thin on the point of actually theming. Most other tutorials, books, etc. follow the documentation in this regard, making really good examples of theming very hard to come by (unless, of course, you only want to change the color of the app from blue to green…if so, you’re covered :)).

This, ultimately, was the reason for my experiments in the last post. I really wanted to try to create something in the new theming process that is more than a new color for a button, or changing the base theme color of the app. Rather, I wanted to significantly alter the appearance of something to demonstrate that not only is the theming process within ExtJS extremely powerful, but ultimately pretty simple to use once you get a feel for it.

So what follows is really just a bit of my experiences. Having just scratched the surface of theming in ExtJS, what I share below is purely my first stab…I’m not suggesting that these are best practices or anything like that. However, hopefully there is something in here that’s helpful for you…I know the pain of the process has been beneficial to my experience!

Create a Theme

I’m not going to waste time walkthrough through the process of generating a custom theme. I followed the steps outlined in the documentation; if you want to follow along with my example, start there, extend the Neptune theme, and call your custom theme “sleek”. Fair enough?

Where To Start

For my test, we want to create a few custom Ext.panel.Panel UIs. These custom UIs should be significantly different from the regular Neptune panels, including:

  • Better typography and text spacing
  • More generous padding
  • Subtle drop-shadow
  • Custom header and background colors

Here’s what we want to end up with. The top panels are default Neptune chrome, and the next two rows are our new custom UIs: “airy” and “callout”.

Panels

Since we’re extending Neptune, let’s start by opening up the following files:

  • ext/packages/ext-theme-neptune/sass/src/panel/Panel.scss
  • ext/packages/ext-theme-neptune/sass/var/panel/Panel.scss

Take a few minutes here and study each file closely. Among other things, we’ll want to decide what variables would be relevant to our custom UIs, as well as noticing any holes in the theme support for other things we want to do (e.g., the drop-shadow).

Next, in order to mimic the Neptune structure, let’s create blank Panel.scss in our own sleek/sass/var/panel and sleek/sass/src/panel folders. Once this is done, we’re ready to start wrestling with SASS. But first, let’s build the package to make sure that all the pieces know about each. In Terminal, navigate to the custom theme directory and execute the following:

sencha package build

Excellent.

Some Strategy

Before we start hacking away at this, let’s think strategically a bit and figure out how we want to tackle this.

  • Our custom UIs have some things in common with default (all have headers, all have body content, all have borders (rounded and straight)
  • Our custom UIs have some things in common with each other (same typography, same padding, etc)

This is an important step if for no other reason than that it’s going to help prevent us from duplicating a lot of code unnecessarily. Wherever we can share/inherit/whatever, the better off we’ll be.

Variables

So then, let’s set up our variables first. In sleek/sass/var/panel/Panel.scss:

// "sleek" panel vars...these will be base vars for our other custom panel uis
$panel-sleek-header-font-family: "Open Sans", Arial, sans-serif !default;
$panel-sleek-header-weight: 600 !default;
$panel-sleek-header-font-size: 13px !default;
$panel-sleek-header-font-weight: bold !default;
$panel-sleek-header-text-transform: uppercase !default;
$panel-sleek-header-line-height: 38px !default;
$panel-sleek-body-line-height: 20px !important;
$panel-sleek-frame-border-color: #d8d9d9 !default;
$panel-sleek-frame-background-color: #fffdf2 !default;
$panel-sleek-header-padding: 15px 20px !default;
$panel-sleek-border-width: 1px !default;
$panel-sleek-header-border-width: 1px !default;
$panel-sleek-body-border-width: 1px !default;
$panel-sleek-frame-border-width: 0px !default;
$panel-sleek-frame-padding: 0 !default;
$panel-sleek-frame-header-border-width: $panel-sleek-frame-border-width !default;
$panel-sleek-frame-header-border-color: #eaeaea !default;
$panel-sleek-frame-header-padding: 15px 20px !default;
$panel-sleek-body-border-width: 1px !default;
$panel-sleek-frame-body-border-width: 1px !default;

// panel-airy vars
$panel-airy-header-color: #349aff !default;
$panel-airy-header-background-color: white !default;
$panel-airy-body-border-color: #d8d9d9 !default;
$panel-airy-tool-background-image: 'tools/tool-sprites-dark' !default;
$panel-airy-frame-background-color: white;
$panel-airy-background-color: white;

// panel-callout vars
$panel-callout-header-color: #fafafa !default;
$panel-callout-header-background-color: #5d6167 !default;
$panel-callout-body-border-color: #d8d9d9 !default;
$panel-callout-tool-background-image: 'tools/tool-sprites' !default;
$panel-callout-frame-background-color: #fffdf2;
$panel-callout-background-color: #fffdf2;

$include-panel-airy-ui: true !default;
$include-panel-airy-framed-ui: true !default;
$include-panel-callout-ui: true !default;
$include-panel-callout-framed-ui: true !default;

As you can see, we start by defining a block of “base” variables containing the prefix panel-sleek-. The idea with these is that

  1. We can overwrite “default” panel vars
  2. We can use these common overrides in any custom UIs that we’d like.

Next, we define the UI-specific variables for each UI, using the prefix panel-{UI name}.

NOTE: Notice that we really have 4 UIs, 2 of which include “frame” in the UI name. These UIs are applied automatically when the “frame” attribute of a panel is used.

Finally, we set a variable for whether to include our custom UIs or not. These are not necessary, but do follow the pattern in the Neptune theme.

Apply the Variables

The next step is to apply the variables. To do this, open sleek/sass/src/panel/Panel.scss:

@if $include-panel-airy-ui {
    @include extjs-panel-ui(
        $ui-label: 'airy',
        // use "sleek" style rules
        $ui-header-font-family: $panel-sleek-header-font-family,
        $ui-header-font-size: $panel-sleek-header-font-size,
        $ui-header-font-weight: $panel-sleek-header-font-weight,
        $ui-header-text-transform: $panel-sleek-header-text-transform,
        $ui-header-padding: $panel-sleek-header-padding,
        // use "airy" style rules
        $ui-tool-background-image: $panel-airy-tool-background-image,
        $ui-header-color: $panel-airy-header-color,
        $ui-header-background-color: $panel-airy-header-background-color,
        $ui-body-background-color: $panel-airy-background-color
    );
}

@if $include-panel-airy-framed-ui {
    @include extjs-panel-ui(
        $ui-label: 'airy-framed',
        // inherit "default" style rules
        $ui-border-radius: $panel-frame-border-radius,
        // use "sleek" style rules
        $ui-border-color: $panel-sleek-frame-border-color,
        $ui-header-font-family: $panel-sleek-header-font-family,
        $ui-header-font-size: $panel-sleek-header-font-size,
        $ui-header-font-weight: $panel-sleek-header-font-weight,
        $ui-header-text-transform: $panel-sleek-header-text-transform,
        $ui-header-border-color: $panel-sleek-frame-border-color,
        $ui-border-width: $panel-sleek-frame-border-width,
        $ui-padding: $panel-sleek-frame-padding,
        $ui-header-border-width: $panel-sleek-frame-header-border-width,
        $ui-header-padding: $panel-sleek-frame-header-padding,
        $ui-body-border-width: $panel-sleek-frame-body-border-width,
        // use "airy" style rules
        $ui-header-color: $panel-airy-header-color,
        $ui-header-background-color: $panel-airy-header-background-color,
        $ui-body-border-color: $panel-airy-body-border-color,
        $ui-tool-background-image: $panel-airy-tool-background-image,
        $ui-body-background-color: $panel-airy-frame-background-color
    );
}

.#{$prefix}panel-airy-framed, .#{$prefix}panel-airy {
    -webkit-box-shadow: 0 2px 3px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(0, 0, 0, 0.07);
    -moz-box-shadow: 0 2px 3px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(0, 0, 0, 0.07);
    box-shadow: 0 2px 3px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(0, 0, 0, 0.07);
    .#{$prefix}panel-header {
        @include background-gradient($panel-airy-header-background-color, matte, top);
        border-bottom: solid 1px $panel-sleek-frame-header-border-color !important;
    }
    .#{$prefix}panel-body {
        line-height: $panel-sleek-body-line-height;
    }
}

Here, we start by using the extjs-panel-ui mixin for our regular and “framed” UIs. As you can see, we pass a collection of arguments, some from our base “sleek” variables, others from our custom “airy” UI variables.

The mixins, of course, only do so much. For some of what we want to accomplish, we’ll have to define our own style rules. In the section of code at the bottom, we do just that:

  • On the main panel element, we apply a box shadow
  • On the panel header, we add a background gradient (via the background-gradient() mixin – ext-theme-base/sass/etc/mixins/background-gradient.scss) which uses one of our custom UI variables as the starting color
  • Specify a default line-height for any text in the panel-body, which gives us a bit more generous spacing on text

Rinse and repeat for the callout UI, and we’re done.

Build and Use

Now that are custom panel UIs are complete, the last step is to re-build our final theme, and then apply the UIs to some panels. First, let’s rebuild. From our sleek package, execute the following:

sencha package build

Finally, find a panel that you’d like to test. To apply the custom UI, simply specify it in the panel config, like so:

{
   xtype: 'panel',
   ui: 'airy',
   ...
}
...
{
   xtype: 'panel',
   ui: 'callout',
   ...
}

Conclusion

As we’ve seen, the new theming engine in ExtJS 4.2+ is really quite powerful and awesome. However, a word of warning: it WILL take a serious commitment of time to be successful with it on a truly full theme. While I think my example above is pretty cool, it is a very small (and admittedly incomplete) execution of what would be required for a fully custom theme that would tie everything together in aesthetic harmony. True enough, t does get easier as you go; however, it is no insignificant task, and you must always take into account the time required between theme recompilations (which are required in order to preview ANY change to you theme).

I don’t say this to deter anyone from the theming path; to the contrary, I really hope that some seriously talented people get involved and give the community an array of amazing theme options. However, you DO need to know what you’re getting into, especially if the theme is tied to a project with deadlines.

Good luck!