Creating Themes
Sometimes you would like to use one widget in two or more applications. For this, usually you want the widget to behave identically, but look differently. Alternatively, sometimes you would like to offer the same application in different appearances. LaxarJS has the concept of themes to help you achieve these things.
Preliminary readings:
Why Themes?
LaxarJS ships with a so-called default.theme, which is actually just Bootstrap CSS together with Font Awesome and a few additional classes. There are several ways to add your own styles.
From Ad-Hoc Styles to Theme Folders…
Usually, you will need to add some CSS classes of your own.
For example, the vast majority of web application needs some styling for the page background and positioning, or some custom header- and footer-areas.
To include such ad-hoc styles, you could simply add a CSS file of your own to the project, and load it from the debug.html
and index.html
files using the <link>
tag.
However, it is better to add these styles to your main application layout instead, into a sub-folder called default.theme/css
.
The benefit of using such a theme folder is that
-
your CSS will be bundled and compressed together with Bootstrap CSS (no additional
<link>
tag needed) and that -
you can support different themes simply by adding more
.theme
folders.
Due to the first point, using theme folders is useful and recommended even if you only use (and maybe customize) the default theme.
…and to Custom Themes
As soon as you use multiple page layouts, the previous approach does not really scale anymore: you would have to duplicate your global styles to all of them. In these cases, creating your own theme is definitely recommended. A detailed explanation of creating a theme is given below.
A Note on SCSS
When using theme folders or entire themes, the LaxarJS loader (used by webpack) will only ever look at .css
files in css
sub-folders by default.
However, it is entirely up to you which (if any) CSS authoring tools you would like to use.
You simply need to configure your widget to load you stylesheet from a different file that has the appropriate extension.
Do this by adding a styleSource
attribute to the widget descriptor, and by setting its value to a path that is relative to the theme folder.
The LaxarJS team uses SCSS to create themes, and the default-theme is based on the SCSS version of Bootstrap. Using this approach makes it very easy to create a custom theme just by changing some Bootstrap SCSS variables. Also, by using SCSS variables defined in the theme, widgets and controls can provide a consistent appearance. After explaining themes in general, further down we give instructions on creating an SCSS theme.
Creating Your Own Theme
Let us create our own theme for an existing application, the LaxarJS ShopDemo. The ShopDemo uses the LaxarJS-branded "cube.theme", which is implemented by augmenting Bootstrap with some changes and custom additions, such as the circle icons used with the headlines.
Above: The LaxarJS ShopDemo using the cube theme
However, the demo also works with just the default theme, provided by LaxarJS UiKit, although admittedly it does not look quite as pretty:
Above: The LaxarJS ShopDemo using the default theme
A Custom Theme Using Plain CSS
Since all applications seem to offer a "dark" look these days, let us try to achieve this for our shop application. Fortunately, there are several collections of nice bootstrap themes available for free. On the site Bootswatch for example, you will find the theme darkly, which looks like it might work for us.
The only thing that is actually required for a theme to work are a configuration entry and a CSS file in the right place.
Put the pre-built darkly CSS into the right place, which is application/themes/darkly.theme/css/theme.css
.
The theme-root (application/themes/
by default) can be changed in LaxarJS projects by setting the export paths.themes
of the file laxar.config.js
.
Add a descriptor containing the canonical name of the theme:
// application/themes/darkly.theme/theme.json { "name": "darkly.theme" }
To use the new theme, make sure to load it along with the application artifacts, by updating the artifacts-import, usually found in the init.js
of the project:
import artifacts from 'laxar-loader/artifacts?flow=main&theme=darkly';
In the LaxarJS bootstrapping configuration (usually also part of the init.js
), set the configuration property theme
to "darkly":
import { create } from 'laxar'; const adapters = [ /* ... */ ]; create( adapters, artifacts, { // ... more configuration ... theme: 'darkly' } );
This causes the LaxarJS runtime to use the new theme.
Because the ShopDemo uses Font Awesome, we need to add an import to the top of our CSS file for that as well:
@import url("//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css");
Before opening the application in the browser, make sure to restart the development server, so that the new files are picked up. And voilà, we have a dark web shop:
Above: The all-new ShopDemo using the darkly theme, hopefully not for any shady business
Of course, there are still some rough spots that need additional work: For example, the widget headers look much better using the original LaxarJS demo theme.
Let's fix that by overriding widget styles from the theme:
For each widget whose styles you want to override, create a stylesheet.
The path relative to your darkly.theme
directory is as follows: ./widgets/<name>/<styleSource>
, where name
and styleSource
are determined by the corresponding fields of the widget.json
descriptor of each widget.
If a descriptor does not specify a styleSource
, it defaults to css/<name>.css
.
Here are some suggestions for a nicer look, to be put under application/themes/darkly.theme/widgets/
:
- article-browser-widget:
article-browser-widget/scss/article-browser-widget.scss
As you can see by the path, this widget uses SCSS to generate its stylesheet. Here we color the icon, the headline to match the logo, and the currently selected article to match the details widget.
.article-browser-widget { // Customize header and icon color: h3 i { color: #F90; } th { background-color: #F90; color: #222222; } // highlight the selected article: tr.selected td { font-weight: bold; background: #3498DB; } }
- article-teaser-widget:
article-teaser-widget/scss/article-teaser-widget.scss
Here we color the icon and the headline to match the button.
.article-teaser-widget { h3 i { color: #3498DB; } h4 { background-color: #3498DB; padding: 8px; } }
- shopping-cart-widget:
shopping-cart-widget/scss/shopping-cart-widget.scss
Again, we color the icon and the headline to match the button.
.shopping-cart-widget { h3 i { color: #00bc8c; } th { background-color: #00bc8c; } .app-increase-quantity { text-align: right !important; } .app-increase-buttons { padding: 0; padding-top: 6px; width: 40px; button { padding: 0; } } }
Of course, we do not want users to download an additional CSS file for each widget that we use.
Instead, we use webpack -P
to create an optimized bundle, which we may load from the index.html
.
Of course, there are still some areas of improvements to this way of styling widgets. For example, if we would like to change the shade of blue that is used in our theme, we would have to update multiple source code locations. It would be better to have some way to define these values in our theme and reuse them from individual widgets.
Adding a Theme using SCSS
To support centralized variables, you can use a compiles-to-CSS language such as SCSS/SASS or less.
Just like widgets, themes can specify an alternate stylesheet location using their theme.json
descriptor.
Fortunately, an SCSS-version of the darkly theme is already available, and can be installed using npm.
The SCSS for our theme is little more than a couple of imports.
Just make sure to adjust the webpack configuration of the project (webpack.config.js
) to add all the import paths needed by your theme's SCSS.
The default.theme and the cube.theme each have sass-options.js that you can use as an example.
The advantage of using an SCSS-based theme is that we can now write concise widget styles using central variables. As an example, the SCSS file for the article-browser-widget now becomes:
@import "variables_all"; .article-browser-widget { h3 i { color: $app-color-logo; } th { background-color: $app-color-logo; color: $body-bg; } tr.selected td { font-weight: bold; background: $brand-info; } }
Which CSS framework and tool chain to use (and if any) is ultimately up to you. The shop demo on GitHub contains the cube.theme styles as well as default.theme styles.
The Bootstrap framework incurs some degree of boilerplate, but makes it relatively easy to reuse widgets across applications, and to find controls that work with your theme out of the box.
How the Runtime Finds CSS
As mentioned above, the LaxarJS runtime and loaders do not care how you create your stylesheet. However, these tools need to find it, so it is important where the CSS files are. For details on how CSS and other assets are loaded, have a look at the asset lookup manual.
In general, the lookup order goes like this:
- If there are theme-specified styles for an artifact (bundled with the application theme in use), then use those,
- if there are theme-specified styles for an artifact (bundled with the artifact itself), then use those,
- else if there are default styles for an artifact then use those,
- else load nothing.
Of course, load nothing means that it is completely fine for a widget not to have any CSS styles.