Chapter 19. Cascading Style Sheets

Table of Contents

1. Overview
2. Simple Views
3. <boxmodel>
4. Selectors
4.1. Attribute selectors
4.2. Element selectors
4.3. ID selectors
4.4. Descendant selectors
5. A Nested View
6. Dynamically-Created Views
7. Dynamic CSS
8. Specificity
8.1. Lexical order
8.2. Importing an external stylesheet

Cascading Style Sheets (CSS) enable web designers to enhance the power of HTML tags. In old-fashioned HTML, an <H1> tag would make displayed text a little bigger and bolder, but font style, color, and size were left to the browser. With CSS, the web designer can use the <H1> tag to specify those font attributes.

CSS support within OpenLaszlo helps designers who are not fluent with the LZX scripting language maintain the appearance of Laszlo applications.

1. Overview

If an OpenLaszlo application is to be deployed more than once — but with different colors, sizes, or resources in each deployment, for instance — the designer can alter those attributes within a stylesheet.

2. Simple Views

This is the simplest example of an OpenLaszlo view:

Example 19.1. Simplest view

<canvas height="120" width="100%">
  <view height="100" width="100" bgcolor="#663399"/>
</canvas>

To generate that view with a cascading stylesheet:

Example 19.2. Simplest view with CSS

<canvas height="120" width="100%">

  <stylesheet>
    #gView {
      height: 100;
      width: 100;
      bgcolor: #663399;
    }
  </stylesheet>

  <view id="gView" height="$style{'height'}" width="$style{'width'}" bgcolor="$style{'bgcolor'}"/>

</canvas>

The $style constraint tells the OpenLaszlo runtime to style an attribute according to the stylesheet. More than one stylesheet can be used in an LZX document; the application of a stylesheet is determined by selectors.

3.  <boxmodel>

The <boxmodel> mixin adds CSS2 model support to <view>.

<boxmodel> implements a subset of the CSS2 box model spec.

For a complete description of <boxmodel>, see the Reference.

Example 19.3. Simple view with boxmodel

<canvas height="200" width="100%" bgcolor="antiquewhite">
  <stylesheet>
    #thebox {
    padding: 1 3 5 7;
    border-width: 2 4 6 8;
    border-color: cornflowerblue;
    margin: 3 7 11 15;
    }
    text { color: navy; }
    .margin { background-color: aliceblue; }
    .borderwidth { color: white; background-color: cornflowerblue; }
    .padding { background-color: skyblue; }
    .content { background-color: white; }
    colortext { border-width: 1; }
    colortext.margin, colortext.borderwidth { border-bottom-width: 0 }
  </stylesheet>
  
  <class name="colorview" extends="view">
    <attribute name="bgcolor" style="background-color" value="transparent"/>
    <attribute name="fgcolor" style="color" value="black"/>
  </class>
  <class name="colortext" extends="text" with="boxmodel" width="100%" multiline="true">
    <attribute name="bgcolor" style="background-color" value="transparent"/>
    <attribute name="fgcolor" style="color" value="black"/>
  </class>
  
  <colorview styleclass="margin" x="5" y="5">
    <colorview id="thebox" with="boxmodel" styleclass="padding" width="250">
      <colorview styleclass="content" layout="axis: y; spacing:0" width="100%">
        <text>Click me to demonstrate updating:</text>
        <text name="op"/>
        <colortext styleclass="margin">
          <handler name="onmargin" reference="thebox" method="display"/>
          <handler name="oninit" method="display"/>
          <method name="display" args="ignore">
            this.format("margin: %s", thebox.presentAttribute('margin'));
          </method>
        </colortext>
        <colortext styleclass="borderwidth">
          <handler name="onborderwidth" reference="thebox" method="display"/>
          <handler name="oninit" method="display"/>
          <method name="display" args="ignore">
            this.format("borderwidth: %s", thebox.presentAttribute('borderwidth'));
          </method>
        </colortext>
        <colortext styleclass="padding">
          <handler name="onpadding" reference="thebox" method="display"/>
          <handler name="oninit" method="display"/>
          <method name="display" args="ignore">
            this.format("padding: %s", thebox.presentAttribute('padding'));
          </method>
        </colortext>
        <attribute name="attribute" type="string"/>
        <attribute name="amount" type="number"/>
        <handler name="onclick" method="doit"/>
        <handler name="oninit" method="doit"/>
        <method name="doit" args="ignore">
          if (this.attribute) {
          thebox.setAttribute(this.attribute, thebox[this.attribute] + amount);
          }
          var r = Math.floor(Math.random()*3);
          var prefix = ['margin', 'border', 'padding'][r];
          var suffix = ['','width',''][r];
          this.attribute = prefix+['top','right','bottom','left'][Math.floor(Math.random()*4)]+suffix;
          this.amount = Math.round(Math.random()*10);
          op.format("%s += %w", attribute, amount);
        </method>
      </colorview>
    </colorview>
  </colorview>
</canvas>

4. Selectors

The stylesheet is a collection of rules that apply to the LZX document. Each rule applies to one aspect of the document, and consists of two parts: selector and declaration.

Example 19.4. Selector

#myView { bgcolor: #0000FF; }

The selector comes before the curly brace, the declaration after. In the example above, an LZX element with id="myView" and bgcolor="$style{'bgcolor'} within its declaration will get a blue background color.

CSS support for OpenLaszlo enables four types of selectors:

  1. Attribute

  2. Element

  3. ID

  4. Descendant

4.1. Attribute selectors

Example 19.5. Attribute example

[attribute="value of attribute"] {
	property: "foo";
	}

The attribute selector applies when it corresponds to a node's attribute (for instance, name or width):

Example 19.6. Attribute selector

<canvas height="120" width="100%">

  <stylesheet>

    [name="PurpleView"] {
      width: 100;
      height: 100;
      bgcolor: #9900FF
    }

    [width="200"] {
      bgcolor: #0000FF;
    }

  </stylesheet>

  <simplelayout axis="x" spacing="10"/>

  <class name="myPurpleView">
    <view name="PurpleView" height="$style{'height'}" width="$style{'width'}" bgcolor="$style{'bgcolor'}"/>
  </class>

  <class name="myWiderView">
    <view name="hoo" height="100" width="200" bgcolor="$style{'bgcolor'}"/>
  </class>

  <myPurpleView/>
  <myWiderView/>

</canvas>

The class myPurpleView contains a view whose name corresponds to the stylesheet selector [name="PurpleView"], so each of its $style rules is applied. The class myWiderView contains a view whose width fires the $style that applies to its background color.

4.2. Element selectors

Example 19.7. Element example

        node {
            property: "foo";
        }

The element selector applies when it matches the tag that creates the node. In the example below, the element selector view matches the <view> tag:

Example 19.8. Element selector

<canvas height="120" width="100%">
  
  <stylesheet>
    view {
      nicebgcolor: #0000FF;
      micebgcolor: #CCCCCC;
    }
  </stylesheet>

  <view height="100" width="100" bgcolor="$style{'nicebgcolor'}"/>
  <view height="75" width="75" bgcolor="0xFFFFFF"/>
  <view height="50" width="50" bgcolor="$style{'micebgcolor'}"/>

</canvas>

4.3. ID selectors

Example 19.9. ID example

        #id {
            property: "foo";
        }

ID selectors are identified by # in the stylesheet. The selector applies when it matches a node's ID:

Example 19.10. ID selector

<canvas height="170" width="100%">

  <stylesheet>

    #red {
      width: 100;
      height: 100;
      bgcolor: #FF3333;
    }

    #blue {
      width: 150;
      height: 150;
      bgcolor: #3333FF;
    }
    
  </stylesheet>

  <simplelayout axis="x" spacing="5"/>

  <class name="gBox" width="$style{'width'}" height="$style{'height'}" bgcolor="$style{'bgcolor'}"/>
  <gBox id="red"/>
  <gBox id="blue"/>

</canvas>

4.4. Descendant selectors

Example 19.11. Descendant example

fee fi {
    property: "car";
}

fee fi foo {
    property: "cart";
}

fee fi foo fum {
    property: "cartman";
}

A selector in an ancestor/descendant hierarchy applies when its ancestors do. In the snippet above, the "fee" selector depends on no ancestors, so it would always fire on an node identified by "fee". The "fum" selector, on the other hand, would only fire if the node had three ancestors "fee", "fi", and "foo".

Example 19.12. Descendant selector

<canvas height="320" width="100%">

  <stylesheet>

    styledbox {
      stylebgcolor: #CCCCCC;
      styleinnercolor: "red";
    }

    styledbox styledbox2 {
      stylebgcolor: #999999;
      styleinnercolor: "blue";
    }

    styledbox styledbox2 styledbox3 {
      stylebgcolor: #666666;
      styleinnercolor: "green";
    }

  </stylesheet>

  <class name="styledbox" height="100" width="100" bgcolor="$style{'stylebgcolor'}">
    <view name="snow" x="10" y="10" height="10" width="10" bgcolor="$style{'styleinnercolor'}"/>
  </class>

  <class name="styledbox2" height="100" width="100" bgcolor="$style{'stylebgcolor'}">
    <view name="braxton" x="10" y="10" height="10" width="10" bgcolor="$style{'styleinnercolor'}"/>
  </class>

  <class name="styledbox3" height="100" width="100" bgcolor="$style{'stylebgcolor'}">
    <view name="jackson" x="10" y="10" height="10" width="10" bgcolor="$style{'styleinnercolor'}"/>
  </class>

  <styledbox id="comet" x="200" y="0">
    <styledbox2 id="shock" x="-100" y="100">
      <styledbox3 id="storm" x="-100" y="100"/>
    </styledbox2>
  </styledbox>

</canvas>

In the example above, the instances of styledbox2 and styledbox3 at the end of the script don't appear on the canvas at all, because there's no stylesheet information for styledbox2 unless it descends from styledbox, and no stylesheet information for styledbox3 unless it descends from stylebox and stylebox2.

[Warning] Warning

Don't include an underscore in selector names, because the LZX file will not compile.

[Warning] Warning

The difference between an attribute selector and a descendant selector is as little as a spacebar. The attribute selector person[name='george'] (no space between person and name) selects a person whose name is george, but the descendant selector person [name='george'] selects a view named george which is a child of a person element.

5. A Nested View

The advantage of using stylesheets shows in a slightly more difficult OpenLaszlo application. If a designer wanted to reuse a template, no knowledge of OpenLaszlo's LZX language is necessary — in this example, s/he just has to change the color hexcode(s) and the name(s) of the resource(s) in the stylesheet:

Example 19.13. Nested view

<canvas height="220" width="100%">

  <stylesheet>

    #sun {
      bgcolor: #16355E;
      face: "resources/smiley.gif";
    }

    #monarchs {
      bgcolor: #B2B9CB;
      face: "resources/sourface.png";
    }

  </stylesheet>

  <simplelayout axis="x" spacing="10"/>

  <class name="bouncebox">
    <view name="outer" x="0" y="0" width="200" height="200" bgcolor="$style{'bgcolor'}">
      <view name="inner" x="50" y="50" bgcolor="0xFFFFFF" width="${immediateparent.width-100}" height="${immediateparent.height-100}"/>
      <view name="gBounce" source="$style{'face'}" x="50" y="50">
        <animatorgroup name="myAnimatorGroup" start="true" process="sequential">
          <animator attribute="y" from="50" to="150" duration="1000" motion="linear"/>
          <animator attribute="y" from="50" to="100" duration="1000" repeat="Infinity" motion="easeout"/>
        </animatorgroup>
      </view>
    </view>
  </class>

  <bouncebox id="monarchs"/>
  <bouncebox id="sun"/>

</canvas>

6. Dynamically-Created Views

For styling a view created dynamically with new, it's easiest to define a class, then new instances of the class:

Example 19.14. Styled views created dynamically

<canvas height="150" width="100%">

  <stylesheet>
    mookie {
      height : 25;
      width : 25;
      bgcolor : #0000FF;
    }

    blaylock {
      title : "Blaylock";
      bgcolor : #FF0000;
    }

    wilson {
      height : 25;
      width : 200;
      fgcolor : #FF00FF;
      text : "Wilson";
    }
  </stylesheet>

  <class name="mookie" x="200" height="$style{'height'}" width="$style{'width'}" bgcolor="$style{'bgcolor'}"/>

  <class name="blaylock">
    <window title="$style{'title'}" x="250" height="100" width="150" bgcolor="$style{'bgcolor'}"/>
  </class>

  <class name="wilson">
    <text text="$style{'text'}" x="400" height="$style{'height'}" width="$style{'width'}" fgcolor="$style{'fgcolor'}"/>
  </class>

  <button text="Dynamically create Mookie">
    <handler name="onclick">
      canvas.mookieView=new lz.mookie(canvas, {});
    </handler>
  </button>

  <button text="Dynamically create Blaylock" y="50">
    <handler name="onclick">
      canvas.blaylockWindow=new lz.blaylock(canvas, {});
    </handler>
  </button>

  <button text="Dynamically create Wilson" y="100">
    <handler name="onclick">
      canvas.wilsonText=new lz.wilson(canvas, {});
    </handler>
  </button>

</canvas>

7. Dynamic CSS

CSS attribute selectors automatically update when the value of an attribute specified by the selector changes. For example: button[mouse=down] will listen for changes to the mouse attribute and will only apply when button.mouse == 'down'.

The following example demonstrates the use of dynamic CSS. Click on the color buttons to toggle the colors; click on the color swatches to see the CSS description.

Example 19.15. Dynamic CSS

<canvas width="100%" height="500" debug="true"> 
  <debug x="30%" width="65%" y="5%" height="90%"/>
  <stylesheet>
    /* default, should only be seen if things are broken */
    colorswatch { background-color: orange }

    /* Static for buttons */
    colorbutton[color=red] {background-color: red }
    /* `lime` is the HTML name for rgb(0,255,0) */
    colorbutton[color=green] {background-color: lime }
    colorbutton[color=blue] {background-color: blue }

    /* dynamic single selectors that apply to individual swatches */
    [red=off] colorswatch { background-color: cyan; opacity: 0.2 }
    [red=on] colorswatch { background-color: red; opacity: 0.95 }
    [green=off] colorswatch { background-color: magenta; opacity: 0.2 }
    [green=on] colorswatch { background-color: lime; opacity: 0.95 }
    [blue=off] colorswatch { background-color: yellow; opacity: 0.2 }
    [blue=on] colorswatch { background-color: blue; opacity: 0.95 }

    /* dynamic compound selectors that apply to mixer colorswatch */
    [red=off] [green=off] [blue=off] [name=mixer] colorswatch { background-color: black; opacity: 1 }
    [red=off] [green=off] [blue=on] [name=mixer] colorswatch { background-color: blue; opacity: 1 }
    [red=off] [green=on] [blue=off] [name=mixer] colorswatch { background-color: lime; opacity: 1 }
    [red=off] [green=on] [blue=on] [name=mixer] colorswatch { background-color: cyan; opacity: 1 }
    [red=on] [green=off] [blue=off] [name=mixer] colorswatch { background-color: red; opacity: 1 }
    [red=on] [green=off] [blue=on] [name=mixer] colorswatch { background-color: magenta; opacity: 1 }
    [red=on] [green=on] [blue=off] [name=mixer] colorswatch { background-color: yellow; opacity: 1 }
    [red=on] [green=on] [blue=on] [name=mixer] colorswatch { background-color: white; opacity: 1 }
  </stylesheet>

 <!-- button to toggle an attribute which should trigger style change -->
  <class name="colorbutton" width="40" height="20" align="center">
    <attribute name="bgcolor" style="background-color"/>
    <attribute name="color" type="string"/>
    <text fgcolor="white" text="${this.parent.on}" align="center"/>
    <attribute name="on" type="string" value="off"/>
    <handler name="onclick">
      this.setAttribute('on', this.on === 'on' ? 'off' : 'on' );
    </handler>
  </class>

  <!-- color swatch that is styled by background-color and opacity -->
  <class name="colorswatch" height="60" width="60" bgcolor="gray" align="center">
    <attribute name="swatchcolor" style="background-color" type="color"/>
    <attribute name="swatchopacity" style="opacity" value="1"/>
    <handler name="onclick">
      Debug.clear();
      Debug.explainStyleBindings(this);
    </handler>
    <view name="swatch" x="5" y="5" height="50" width="50">
      <attribute name="bgcolor" value="${parent.swatchcolor}"/>
      <attribute name="opacity" value="${parent.swatchopacity}"/>
    </view>
  </class>

  <!-- nested views with red, green, blue attributes that dynamically change
       to demonstrate the applicability of compound dynamic selectors  -->
  <view x="5%" y="5%" width="20%" height="80%" layout="axis: y; spacing: 5">
    <text width="100%" multiline="true">
      Toggle the red, green, and blue buttons on and off to
      dynamically update the applicable CSS selectors.  Click on a color
      swatch to explain the applicability of the CSS selectors.
    </text>
    <attribute name="red" value="${this.toggle.on}" type="string"/>
    <colorbutton name="toggle" color="red"/>
    <colorswatch/>
    <view width="100%" layout="axis: y; spacing: 5">
      <attribute name="green" value="${this.toggle.on}" type="string"/>
      <colorbutton name="toggle" color="green"/>
      <colorswatch/>
      <view width="100%" layout="axis: y; spacing: 5">
        <attribute name="blue" value="${this.toggle.on}" type="string"/>
        <colorbutton name="toggle" color="blue"/>
        <colorswatch/>
        <view width="100%" name="mixer" layout="axis: y; spacing: 5">
          <text align="center">Mixer</text>
          <colorswatch id="mixer"/>
        </view>
      </view>
    </view>
  </view>
</canvas>

8. Specificity

An instance of a node -- most likely a view, or some other OpenLaszlo element -- can trigger more than one selector in the stylesheet. In such cases, CSS gives precedence according to rules of specificity. In general, the more specific the selector, the higher its precedence. For instance, the ID of a view is more specific (can only apply to one view) than view itself (applies to every view).

Within OpenLaszlo's CSS support, the order of specificity is (in ascending order):

  1. Element

  2. Attribute

  3. ID

This example uses all three selectors:

Example 19.16. Specificity

<canvas height="70" width="100%">

  <stylesheet>

    view {
      height: 50;
      width: 50;
      bgcolor: #FF0000;
    }

    [name='blue'] {
      height: 40;
      width: 40;
      bgcolor: "blue";
    }

    #green {
      height: 30;
      width: 30;
      bgcolor: rgb(0,102,0);
    }

  </stylesheet>

  <view layout="axis: x; spacing: 25">
    <view id="red" name="red" width="$style{'width'}" height="$style{'height'}" bgcolor="$style{'bgcolor'}"/>
    <view id="blue" name="blue" width="$style{'width'}" height="$style{'height'}" bgcolor="$style{'bgcolor'}"/>
    <view id="green" width="$style{'width'}" height="$style{'height'}" bgcolor="$style{'bgcolor'}"/>
  </view>

</canvas>

The first view -- red -- fires the element selector, because its ID is not "green", and its name is not "blue". It is an instance of a "view" element, which triggers the element selector. The second view -- blue -- fires the name selector; its ID is not "green", but the next selector in order of priority is attribute, which name='blue' matches. The third view -- green -- hits the highest-priority selector; id='green' matches the ID selector #green, and looks up that stylesheet.

[Warning] Warning

Style selectors: Under W3C's CSS specificity rules, the highest-priority selector is "style='foo'", which directly looks up the foo stylesheet. The OpenLaszlo implementation does not support "style='foo'" because that expression cannot be used as a Laszlo property or attribute.

8.1. Lexical order

In some cases, more than one selector of the same type will be triggered. In this example, the view short fires two selectors: [height="50"] and [width="100"]. Neither selector takes precedence because they are both attribute selectors, so a tiebreaker has to be invoked. The selector closest to the bottom of the stylesheet prevails.

Example 19.17. Lexical order

<canvas height="120" width="100%">

  <stylesheet>

    [height="50"] {
      height: 50;
      width: 100;
      bgcolor: #FF0000;
    }

    [width="100"] {
      height: 50;
      width: 100;
      bgcolor: #0000FF;
    }

  </stylesheet>

  <simplelayout axis="y" spacing="10"/>

  <view name="short" width="100" height="50" bgcolor="$style{'bgcolor'}"/>

  <view y="75">
    <text text="If I'm red, the first stylesheet was triggered."/>
  </view>

  <view y="100">
    <text text="If I'm blue, the second stylesheet was triggered."/>
  </view>

</canvas>

8.2. Importing an external stylesheet

A stylesheet can be imported by using

Example 19.18. Imported stylesheet clipping

   <stylesheet src="foo.css" />

when the stylesheet is in its typical format:

Example 19.19. Imported stylesheet example


<!--begin foo.css-->

#gPhilip {
    width: 300;
    height: 200;
    bgcolor: #8F008F;
}

<!--end-->

<!--The LZX file-->

<canvas>

	<stylesheet src="foo.css" />

	<view id="gPhilip" width="$style{'width'}" height="$style{'height'}" bgcolor="$style{'bgcolor'}"/>

</canvas>