Theming Your App
This guide covers restyling SproutCore's built-in controls in your app. You'll learn to:
- Give your app its own theme.
- Theme SproutCore controls like SC.ButtonView.
- Create a theme that you can reuse and share with others.
1 - What is a Theme?
Themes determine how all of the views in your app render. A theme can be as simple as a collection of CSS files that just tweaks some styles, or can be sophisticated enough to replace the DOM generated by the views.
1.1 - The Simplest Theme
Create a CSS file in resources/. Add some styles. You have now given your app its own theme.
For example, you can now try to override styles for an Ace 2.0 button, regular size:
apps/my_app/resources/stylesheets/my_style.css.ace.sc-regular-size.sc-button-view { background: blue }
The .ace class name won't work if your app was created before the change to Ace 2.0 as the default theme. See [Giving Your App a Theme](#giving-your-app-a-theme).
There are a few problems with this:
- You have to write a lot of class names to override anything.
- Changing themes will break all of your CSS.
2 - Giving Your App a Theme
If your app already has a theme.js file in its root, you generated it with a modern version of the SproutCore build tools, and can skip directly to [Styling a Button](#styling-a-button below).
Otherwise, you'll need to add a theme to your app manually.
2.1 - Ace 2.0
This guide assumes you are using Ace 2.0 or a theme easily overridable like it. To switch your app to Ace 2.0, you need to set the :theme setting in your Buildfile:
Buildfile
config :all, :required => :sproutcore, :theme => 'sproutcore/ace'
2.2 - theme.js
A SproutCore theme has both JavaScript and CSS elements. Therefore, to give your app a theme, you will need to add a theme.js to it.
Create a theme.js file in your app's folder:
apps/my_app/theme.jsMyApp.Theme = SC.AceTheme.create({ // This name will identify the theme. Make it good. :) name: 'my-app' }); // Tell SproutCore about your theme SC.Theme.addTheme(MyApp.Theme); // Make it the default theme SC.defaultTheme = 'my-app';
If you look in Web Inspector, you'll notice that all the views in your app are using the my-app class name:
2.3 - Class Names
When you use a SproutCore theme, SproutCore will automatically apply one or more class names to every view that it creates. Because these class names are added to the generated CSS at build time, you will need to specify them in your Buildfile.
If you are using Ace, and your app's own theme is named my-app, your theme's class names will be ace and my-app. To tell the CSS preprocessor the class names, add a line like this to your Buildfile:
Buildfile
# Where my_app is your app's name as it appears in your apps/ folder.
config :my_app, :css_theme => 'ace.my-app'
Now you're all set up! Again, new apps will already have all of this generated automatically.
3 - Styling a Button
Blue is so overused lately, so we want to swap all blue buttons for red. Here are the images for the normal and active states:
Save them both in your resources/ folder to get started.
Now, create a new CSS file in your resources/ folder. To keep things organized, call it button.css. In button.css, put:
apps/my_app/resources/button.css$theme.button { @include slices("button.png", $left: 3, $right: 3); color: white; text-shadow: 0px -1px 0px rgba(0, 0, 0, 1); /* & is SCSS's way of continuing the parent selector; this translates to: $theme.button.active */ &.active { @include slices("button-active.png", $left: 3, $right: 4); } }
Save and reload your app: all your buttons should now be red.
Now, let's go over what that block of CSS is doing.
3.1 - Chance: SproutCore's CSS Preprocessor
SproutCore has its own CSS preprocessor: Chance. It does many things:
- Keeps track of theme class name(s)
- Slices images
- Handles multi-slice scaling
- Includes images/slices as data: urls
- Comes with SCSS built-in
These will all be very useful in styling our button. For instance, the class name tracking gives us the $theme variable:
$theme.button { }
The $theme variable automatically gets replaced with the right class names for our theme.
SproutCore gets your theme's class names from the Buildfile's :css_theme property. If you switch from Ace 2.0 to another theme, you'll need to change the class names in the :css_theme property as well. See [Changing Your Theme](#changing-your-theme).
button is the name you use to address SC.ButtonViews. To find the name for other views, look for class names in Web Inspector. Views typically have a number of classes. They will have a simple name, like button, and longer names, like sc-button-view.
In general, you will want to use the simple name if available. Do Not use the sc-*-view form unless there is no simple name. For instance, always use checkbox instead of sc-checkbox-view.
3.2 - Adding Images
Buttons have three slices: left, middle, and right.
You can address the slices one-by-one:
apps/my_app/resources/button.css$theme.button { // notice that we are using SCSS .left { } .middle { } .right { } }
Our button is currently in one piece. We need to slice it into three parts, cutting 3px from the edge:
In conjunction with Chance's @include slice directive, you could theme the button right now:
apps/my_app/resources/button.css.left { @include slice("button.png", $left: 0, $width: 3) } .middle { @include slice("button.png", $left: 3, $width: 1, $repeat: repeat-x); } .right { @include slice("button.png", $right: 0, $width: 3); }
The @include slice directive lets you specify an image, an optional rectangle to extract from that image, and a few other settings such as $repeat. For full reference, see the guide on Chance.
While you could theme the button this way, there is a better, easier way.
3.3 - @include slices
We mentioned that one of Chance's features handled multi-slice scaling. This feature is the @include slices() directive:
$theme.button { @include slices("button.png", $left: 3, $right: 3); }
You just pass @include slices the image and how far from the edges to cut. It will create as many slices as necessary (up to 9 if you cut away from each edge).
Adding the active state is similar:
apps/my_app/resources/button.css$theme.button { @include slices("button.png", $left: 3, $right: 3); // & is SCSS's way of continuing the parent selector; // this translates to: $theme.button.active &.active { @include slices("button-active.png", $left: 3, $right: 4); } }
3.4 - Finishing Touches
You're almost done! But your button still doesn't look very good: the text style from Ace is carrying over and clashes horribly with the red. Let's fix it:
apps/my_app/resources/button.css$theme.button { @include slices("button.png", $left: 3, $right: 3); color: white; text-shadow: 0px -1px 0px rgba(0, 0, 0, 1); // & is SCSS's way of continuing the parent selector; // this translates to: $theme.button.active &.active { @include slices("button-active.png", $left: 3, $right: 4); } }
Now it looks much better.
4 - "All Buttons"
Red is nice, but it is also quite strong. Maybe you don't want all buttons to use it, after all.
Maybe red buttons would make more sense if there's an error, or if they are for cancelling something. Or maybe you only want one button to get the style.
4.1 - An "Error" Theme
SproutCore apps are not limited to one theme. Perhaps you'd like to use a different theme for a toolbar, or an error dialogue, or even just a single button.
Usually, you'll want additional themes to be based on your app's theme. That means you will want to subclass your theme. There is a long way:
apps/my_app/theme.jsMyApp.Theme.Error = MyApp.Theme.create({ name: 'error' }); // Instead of adding it to SC.Theme, you are adding // it to your own theme. MyApp.Theme.addTheme(MyApp.Theme.Error);
And, better yet, a short way:
apps/my_app/theme.jsMyApp.Theme.Error = MyApp.Theme.subtheme('error');
They do the same thing, but one's quicker.
Now, you just need to alter the button's CSS to target your new theme:
apps/my_app/resources/stylesheets/my_error_style.css/* the @theme directive puts code into a child theme. If before the class names were "ace.my-app", they will now be "ace.my-app.error" */ @theme(error) { $theme.button { @include slices("button.png", $left: 3, $right: 3); color: white; text-shadow: 0px -1px 0px rgba(0, 0, 0, 1); /* & is SCSS's way of continuing the parent selector; this translates to: $theme.button.active */ &.active { @include slices("button-active.png", $left: 3, $right: 4); } } }
Your Error theme is done. Now you just need to make some buttons use it:
// you can set it on a button:
SC.ButtonView.design({
title: "My Button",
themeName: 'error',
// ...
})
// You can set it on a button's parent:
SC.ToolbarView.design({
themeName: 'error',
childViews: 'button'.w(),
button: SC.ButtonView.design({
title: "My Button",
// ...
})
})
Themes are inherited, so your button will get its parent's theme unless you tell it to use another. If you do give it a theme name, it will look for it first inside its parent view's theme. That's how it finds 'error' even though it is not registered globally, but only inside MyApp.Theme.
Like any other theme, your 'error' theme can tweak multiple controls. For instance, should an 'error' toolbar, in addition to having red buttons, itself be red?
4.2 - isCancel
You can tell if a button is a cancel button by the presence of the cancel class:
apps/my_app/resources/stylesheets/my_error_style.css$theme.button.cancel { @include slices("button.png", $left: 3, $right: 3); color: white; text-shadow: 0px -1px 0px rgba(0, 0, 0, 1); /* & is SCSS's way of continuing the parent selector; this translates to: $theme.button.active */ &.active { @include slices("button-active.png", $left: 3, $right: 4); } }
4.3 - Specific Button
If you just want the style to apply to a single button, first add a class name to the button:
SC.ButtonView.design({
classNames: ['my-special-button']
// ...
});
Now, target that class name:
apps/my_app/resources/stylesheets/my_error_style.css$theme.button.my-special-button { @include slices("button.png", $left: 3, $right: 3); color: white; text-shadow: 0px -1px 0px rgba(0, 0, 0, 1); /* & is SCSS's way of continuing the parent selector; this translates to: $theme.button.active */ &.active { @include slices("button-active.png", $left: 3, $right: 4); } }
5 - Changing Your Theme
Your app now has its own theme, but by default, it's based on Ace 2.0.
What if someone writes a super awesome new theme, SuperAwesomeTheme? You'd like to switch from Ace to the new, awsomer theme.
5.1 - Including SuperAwesomeTheme and changing $theme
To include SuperAwesomeTheme, you'll need it in your project's themes/ folder. For example, you might place it in: themes/super_awesome.
Then, you modify your Buildfile to reference it:
Buildfile
# you can target :all, or just :my_app
config :all, :required => :sproutcore, :theme => "super_awesome"
While you are editing the Buildfile, you should also tell Chance about the new value for $theme. The original was probably something like ace.my-app, but ace no longer applies, does it? Use the :css_theme setting to change it:
Buildfile
config :my_app, :css_theme => 'super-awesome.my-app'
5.2 - App's Base Theme
Your app's theme is created in its theme.js file. By default, it is based on SC.AceTheme, and is created roughly like this:
apps/my_app/theme.jsMyApp.Theme = SC.AceTheme.create({ name: 'my-app' })
Just swap out SC.AceTheme with SuperAwesomeTheme:
apps/my_app/theme.jsMyApp.Theme = SuperAwesomeTheme.create({ name: 'my-app' })
Now you've switched the theme!
6 - Making Your Theme Reusable
The above instructions guide you through adding a theme to your app. But what if you want to reuse this theme or share it with others?
Run this command:
sc-gen theme MyTheme
It will add a my_theme folder to a themes/ directory in your project. Like your app, it will have a resources/ folder and a theme.js file.
Move all of your CSS into the theme's resources/ folder (or, at least, all CSS you want to share). If you are using $theme everywhere, your CSS should work as-is.
If you have any child themes, like the 'error' theme we created above, you'll have to add it to your theme's theme.js and remove it from your app's:
apps/my_app/theme.jsMyTheme.Error = MyTheme.subtheme('error');
7 - Changelog
- January 17, 2011: initial version by Alex Iskander
- March 2, 2011: added filenames and fixed formatting by Topher Fangio
- October 30, 2013: converted to Markdown format for DocPad guides by Topher Fangio