XForms Technique: Tabbed Interfaces

Steven Pemberton, CWI Amsterdam

Version: 2020-08-23.

Tabbed Interfaces

An early introduction to XForms [1], showed a simple tabbed-interface technique. Something like this:

Source

This uses four triggers (the XForms presentation-neutral term for what is called a button in HTML), each of which selects one case from an XForms switch:

<trigger id="togglehome" appearance="minimal" label="Home">
   <toggle case="home" ev:event="DOMActivate"/>
</trigger>

and the switch contains the four cases:

<switch>
   <case id="home">
      <label>Home</label>
         Welcome to our home page...
      </case>
      <case id="products">
         ...
</switch>

The rest is styling.

As you can see, it uses a visual trick to make it clear which tab has been clicked on: the label of each tab uses the same colour as its corresponding section header.

A Different Styling

There are cases where you would like all tabs to have the same colour except the one that has been selected, like this:

Source

The problem here is that CSS doesn't natively support such a thing, and therefore you have to save some state to say which tab has been selected, and use that. We will show a couple of ways to do it.

Triggers

The first version will use the same method: four triggers each selecting one case from a switch, but in addition, recording that they have been clicked on. First, an instance to record that, with the home tab initially selected:

<instance id="tab">
   <tab xmlns="">
      <home>selected</home>
      <products/>
      <support/>
      <contact/>
   </tab>
</instance>

Then each trigger will clear any other selected case:

<setvalue ref="instance('tab')/*[.='selected']"/>

before marking its own value as selected:

<setvalue ref="instance('tab')/home">selected</setvalue>

Putting it together:

<trigger>
   <label>Home</label>
   <action ev:event="DOMActivate">
      <toggle case="home"/>
      <setvalue ref="instance('tab')/*[.='selected']"/>
      <setvalue ref="instance('tab')/home">selected</setvalue>
   </action>
</trigger>

And then we add the styling. Each trigger will be of class tab, and optionally of class selected:

<trigger appearance="minimal" class="{instance('tab')/home} tab">
   <label>Home</label>
   <action ev:event="DOMActivate">
      <toggle case="home"/>
      <setvalue ref="instance('tab')/*[.='selected']"/>
      <setvalue ref="instance('tab')/home">selected</setvalue>
   </action>
</trigger>

The CSS then has styling for the classes tab (i.e. unselected), tab selected, and we may as well add tab hover (which CSS does support natively):

 .tab          {background-color: blue; color: white; ... other stuff ...}
 .tab:hover    {background-color: #aaf; color: black}
 .tab.selected {background-color: #ddf; color: black}

Data-based Switching

The switch control always has one, and only one, of its cases visible (the first one initially by default). You change which case is visible using the toggle actions, as we saw above.

You can make content optionally visible by making one of the cases empty:

<switch>
   <case id="hide"/>
   <case id="show">Now you see it</case>
</switch>

Add two triggers:

<trigger label="hide">
   <toggle case="hide" ev:event="DOMActivate"/>
</trigger>
<trigger label="show">
   <toggle case="show" ev:event="DOMActivate"/>
</trigger>

Like this:

Source

You can achieve the same result using data from an instance. If a control, any control, is bound to a value that is not relevant, the control is not displayed. Controls include group, so if you have

<group ref="visible">
   Now you see it
</group>

then if the value visible is not relevant, you won't see the content.

We create the value:

<instance>
   <data xmlns="">
      <visible>show</visible>
   </data>
</instance>

and say when it is relevant:

<bind ref="visible" relevant=".='show'"/>

(in other words, it is only relevant when its value is 'show'), we can then change the triggers accordingly:

<trigger label="hide">
   <setvalue ref="visible" ev:event="DOMActivate"/>
</trigger>
<trigger label="show">
   <setvalue ref="visible" ev:event="DOMActivate">show</setvalue>
</trigger>

like this:

Source

So we can do our tabbed interface using such data-based switching. Each of our four sets of content will be a group linked to a value that changes relevance:

<group ref="instance('tab')/home">
   <label>Home</label>
   Welcome to our home page...
</group>
<group ref="instance('tab')/products">
   <label>Products</label>
   We produce many fine products ...
</group>
 ...

etc.

We create a suitable instance for these values:

<instance id="tab">
   <tab xmlns="">
      <selected>home</selected>
      <home/>
      <products/>
      <support/>
      <contact/>
   </tab>
</instance>

and say under what circumstances the values are relevant:

<bind ref="instance('tab')">
   <bind ref="home" relevant="../selected='home'"/>
   <bind ref="products" relevant="../selected='products'"/>
   <bind ref="support" relevant="../selected='support'"/>
   <bind ref="contact" relevant="../selected='contact'"/>
</bind>

which all say that the respective element is relevant if the value of selected matches.

Now all we have to do is to set up the four triggers:

<trigger label="Home">
   <setvalue ev:event="DOMActivate" ref="instance('tab')/selected">home</setvalue>
</trigger>

etc, and then we have this:

Source

which we then style correctly:

Source

Select1

One observation is that the four triggers together now have more-or-less the same functionality as a select1. So let's do a version with one of those:

Now instead of the four triggers, we have a select1:

<select1 id="tabs" ref="instance('tab')/selected" appearance="full">
   <item><label>Home</label><value>home</value></item>
   <item><label>Products</label><value>products</value></item>
   <item><label>Support</label><value>support</value></item>
   <item><label>Contact</label><value>contact</value></item>
</select1>

Source

We arrange the items horizontally:

Source

Hide the radio buttons:

Source

And style the rest suitably:

Source

(This is not a tutorial on CSS, so check the source to see how the styling was done.)

XForms 2.0

In XForms 2.0 [2] much of this will be easier: the switch element will have an optional ref attribute to select the cases, and you will be able to say:

<select1 id="tabs" ref="instance('tab')/selected" appearance="full">
   <item label="Home">home</item>
   <item label="Products">products</item>
   <item label="Support">support</item>
   <item label="Contact">contact</item>
</select1>
<switch ref="instance('tab')/selected">
   <case name="home" label="Home">
       Welcome to our home page...
   </case>
   <case name="products" label="Products">
       We produce many fine products ...
   </case>
   <case name="support" label="Support">
       For support please ring the following toll-number ...
   </case>
   <case name="contact" label="Contact">
       For all contact, send an email to ...
   </case>
</switch>

References

[1] Steven Pemberton, XForms 1.1 Tutorial Part 2, W3C and CWI, 2016, http://www.cwi.nl/~steven/xforms11-for-html-authors/part2.html.

[2] Erik Bruchez et al., XForms 2.0, W3C 2020, https://www.w3.org/community/xformsusers/wiki/XForms_2.0#The_switch_Element