Theme Settings and New Advanced Controls

To continue a recent string of theme related posts, I'm going to add another on the recently added feature allowing addition of advanced input controls and behaviors to theme settings.

Themes are the whip cream that bring flavour and sweetness to our portal fruit salads. Without beautiful and clever theme designs our fruit salad may taste healthy but can be bland and leave us wanting other treats.

To make sure this doesn't happen, Liferay tries its hardest, each generation, to bring new and/or improved features and richness to the theme APIs in order to empower designers to create master pieces.

One of the tools in the deisgners chest which was added in 6.0 and which added pretty significant flexibility to theme developers was the Settings API.

As the name implies, the Settings API is a mechanism that allows a designer to add configuration settings to their theme creations. These settings can then be used within the theme logic to do things like toggle features on or off, provide lists of options for display behaviors, provide boiler plate content fragments, etc. Using this API the designer can, with a single theme, attempt to provide a mixture of options that can potentially meet many more requirements and appeal to more users.

Using the Settings API

In order to use the settings API the designer must include a liferay-look-and-feel.xml file in their theme. The path should be:

<theme>/docroot/WEB-INF/liferay-look-and-feel.xml

As you might have guessed this is an xml file and a boiler plate file for 6.1 could be as trim as the following:

<?xml version="1.0"?>
 <!DOCTYPE
    look-and-feel PUBLIC
    "-//Liferay//DTD Look and Feel 6.1.0//EN"
    "http://www.liferay.com/dtd/liferay-look-and-feel_6_1_0.dtd">

<look-and-feel>
    <compatibility>
        <version>6.1.0+</version>
    </compatibility>
    <theme id="hotel" name="Hotel Theme" />
</look-and-feel>

Plainly enough this file defines the compatibility the theme has with different portal versions (in this case, since we're using features from 6.1 DTD, we're limiting our compatibility to 6.1+). It defines the theme element which contains the id of the theme as well as the name that will appear in the UI during theme selection/configuration.

In order to add settings we're going to add an element as a child of the <theme> element:

<?xml version="1.0"?>
 <!DOCTYPE
    look-and-feel PUBLIC
    "-//Liferay//DTD Look and Feel 6.1.0//EN"
    "http://www.liferay.com/dtd/liferay-look-and-feel_6_1_0.dtd">

<look-and-feel>
    <compatibility>
        <version>6.1.0+</version>
    </compatibility>
    <theme id="hotel" name="Hotel Theme" >
        <settings>
        </settings>
    </theme>
</look-and-feel>

Each setting is defined using a single <setting> elements with two main attributes, key (the name by which the setting is handled) and value (in the case of configurable setting, this will be the default value).

e.g.

...
<settings>
   <setting key="show-breadcrumb" value="true" />
</settings>
...

There are three optional attributes (added in 6.1):

  • configurable (whether or not the setting is static or editable from the UI)
  • type (the data type for the field: checkbox, select, text, or textarea, default is text if not provided)
  • options (in the case of type="select", options should contain a comma delimited list of values available to the selection)

Therefore a slightly more advanced setting might be:

...
<settings>
    <setting configurable="true" key="show-breadcrumb" type="checkbox" value="true" />
 </settings> 
...

So, how would this be used from the theme?

The usage is pretty simple and is handled by a single method on the ThemeDisplay object; getThemeSetting(String key). The ThemeDisplay object you say? Do not fear, this object is already in the context of both Velocity and Freemarker. In Velocity it is available as both $themeDisplay, as well as $theme_display. In Freemaker it's available as themeDisplay as well as theme_display.

Using the setting we created above, in your theme's portal_normal.vm (in a Velocity theme) you might do the following:

#set ($show_breadcrumb = $theme_display.getThemeSetting('show-breadcrumb'))
 
#if ($show_breadcrumb == 'true')
    <nav class="site-breadcrumbs" id="breadcrumbs">
        <h1>
            <span>#language("breadcrumbs")</span>
        </h1>
        #breadcrumbs()
    </nav>
#end

Checkbox type fields have string values of either "true" or "false".

Advanced controls

So far I have not talked about the Advanced Controls I referred to in the title of the post.

So you have controls, fine! Checkboxs, text fields, textareas, and selects are great but we can't exactly call this a great collection of "rich" controls. These are the lowest common controls that you'd probably expect. We're talking about theme developement here, so we want some POP and PIZZAZ (yes PIZZAZ!!!).

To solve this limitation without trying to provide for every scenario imaginable we added the ability for a setting definition to contain a block of javascript code which would be inlined immediately with the input field. This javascript would allow the designer to take the basic field types and turn them into a more advanced control.

For the following example I'm going to bind a color picker tool to a regular text field.

First the base xml for the definition:

...
<setting 
    configurable="true"
    key="portal-main-color"
    type="text"
    value="#EEF0F2">
<![CDATA[
]]>
</setting>
...

Now, I'm going to use the Alloy UI color-picker component and some other simple code for making the field read-only so users can't input faulty data:

...
<setting 
    configurable="true"
    key="portal-main-color"
    type="text"
    value="#EEF0F2">
<![CDATA[ 
    AUI().ready(
        'aui-color-picker-base',
        function(A) {
            var target = A.one(' #[@NAMESPACE@]portal-main-color');
            target.attr('readonly', 'readonly');
            var currentValue = target.val();
 
            target.ancestor(
                '.aui-field-element').append(                 "<div id='[@NAMESPACE@]PortalMainColorPicker'></div>");
 
            setTimeout(function() {
                var [@NAMESPACE@]PortalMainColorPicker = new A.ColorPicker(
                    {
                        hex: currentValue
                    }
                ).render('#[@NAMESPACE@]PortalMainColorPicker');
 
                [@NAMESPACE@]PortalMainColorPicker.on(                     'colorChange',
                    function(event) {
                        var hex = [@NAMESPACE@]PortalMainColorPicker.get("hex");
 
                        target.val('#' + hex);
                    }
                );
            }, 0);
        }
    );
]]> 

</setting> ... 

If you read through the javascript you will see that we get an element using the A.one() alloy call. This is akin to document.getElementById() but it works by using css selectors. In this case we're getting the setting field by using the key of the field prefixed by the # symbol, which in CSS means that it's the ID of an element. The key is also prefixed by the token [@NAMESPACE@]. This token is used to avoid namespace collisions with other applications that may be floating around the page and is replaced at runtime with the actual namespace of the theme configuration app (in a portal we always need to be conscious that we may be co-existing with other apps). Use the token whenever creating public variables or dom objects.

Once we have the field, we can manipulate it however we like.

The Advanced Control Result

We've added the control, so what does it look like?

Here is the settings panel with the new field and the control:

With the control active:

Moving the cursor around the color-picker while left button pressing on the mouse will move the color-picker cursor and update the value in the setting text field until you release the button.

The Possibilities

Given the multitude of advanced controls we can create using javascript and the simple usage of the Settings API, the possibilities are quite limitless.

I hope that this takes your theme development to a whole new level of creativity and richness.

I'll be at WCS doing a couple of talks and a workshop which I'll be talking about shortly. So if you have questions regarding any of the things I've writen about recently, or about any other topic actually, and you're attending WCS, please bring those along with you.

See you at WCS!

Update: I wanted to add that the Theme Settings feature was originally implemented and donated to Liferay as part of a community contribution by José Ignacio Huertas (LPS-12468). I'd like to thank José for his contribution. This is a very good example of how a small contribution by the community can become a very cool feature. I would like to encourage others to participate by donating their feature implementations, suggestions, bug reports, documentation skills, or translation skills. This is the key way that Liferay has become the rich platform that it is.

Blogs
Nice Feature Ray !!!
Which LR version supports this feature ??
Looking good Ray...what abt the performance issue of loading the theme if many settings are used at the same time??
Awesome feature Ray...thanks for sharing!!
@Jignesh, Settings exist in a simple key value pair from in 6.0. The more advanced use that I describe here is for 6.1.

@KK, I don't foresee any great performance penalty for using many settings themselves. I would take more care though in the implementation of features that use settings. Implementing a costly synchronous external request in the theme that is configured or managed by a setting could have devastating performance consequences, but doesn't really fault the setting itself.
Ray, thank for the nice description. Just a few questions:

This functionality will only be available since 6.1 - did I understand this correctly?
Where will these controls appear - are we talking about the plugin configuration page in control panel or some other place?
Nevermind, I already found the functionality in 6.1 beta
Ok, I'm glad you found it!

Sorry for the late response!
Where will these controls appear - are we talking about the plugin configuration page in control panel or some other place?
hello. nice tut's
Can we use this tips for upload a banner for the theme ?
Hmm. Should be possible..

However, it would require you to provide a UI portion (which you could easily do) which delegates to a Documents and Media edit/upload view then collects the resulting asset url and stores than in the theme properties.
Can this theme setting be accessed from Journal Article template (VM)?

When I output $request() in a Web Content Display I see that it includes the
VIRTUAL_HOST_LAYOUT_SET which in turn includes a themeId=classic and settings=, So it appears we should be able to access it, but it seems this themeId always returns classic, even if I set my page and my portal default theme to my custom theme (I've tried this both in the control panel and via portal-ext.properties).

If it helps, the driving idea here is that I would like to use a theme setting to alter the site logo and other global elements (via logic in portal_normal.vm) based on the setting. I have this working with no issues. However, I'd like to also be able to access the setting from a WCD template (possibly a macro) to be able to output "The Brand Name" or "The Other Brand Name" in a WCD to mirror the logo change. Is this possible? If so I would greatly appreciate a bump in the right direction! Thanks in advance!

Ryan
Hi, I have a question regarding the settings. if i add a settings with key 'show-powered-by', is there any way to add the language value for the same?
Right now this shows up as "show-powered-by" in the GUI, Can it be shown as "Show Powered By"? I believe this is done for the variables of the built-in themes using the Language.properties supplied in the root context, and changing the same using hooks will do the trick to show the show-powered-by correctly. But is there a way to supply your own Language.properties with the theme to support this?
[...] Theme Settings and New Advanced Controls - Blog | Liferay To continue a recent string of theme related posts, I'm going to add another on the recently added feature allowing addition of advanced... [...] Read More
Awesome feature , I have implemented.
in my case there are more then 10 setttings I want to devide that settings tab in more categories is it possible? please guide me how can I do that

I want more accordian under settings

Thanks
Mitul
Ray
I've attempted the color picker but it didn't appeared. I can only see a readonly text input with color number but no option to choose color at all. I'm using Liferay 6.2 EE sp11.

In addition to this I want to achieve following scenario, it would be great if you provide some hint or refer any tutorial to learn more about using advance controls
We have two settings (based on checkbox control), I want to disable the second if first one unchecked. I tried to locate the control based as below
var source = A.one('#[@NAMESPACE@]option-one');
var target = A.one('#[@NAMESPACE@]option-two');

It give me the values for checkbox with target.val() but how to set the value (check/uncheck) and enabled/disabled?

When I tried to attached event on source checkbox it didn't worked either. I tried to alert (source) that not the exact checkbox input like when we work using regular html & javascript.
Why it is not taking configurable type as "file" in look and feel?
Hi, can anyone help me with this kind of issue ? Don't know why is not working:

https://web.liferay.com/community/forums/-/message_boards/message/73771657

Thank you
how to add file type="file"

<setting configurable="true" type="file" key="Select_file" value="" ></setting>
I tried but not getting proper output even I am not getting error also