Extending Controls

This document describes how to extend existing controls and write new controls in markup.

Introduction

Like most object oriented frameworks, Jitsu provides the ability to define types that are based on (or subclass) other types, adding additional behavior as necessary. This can either be done in markup or through script. In this document, we will focus on extending controls through markup.

Subclassing Controls in Markup

Jitsu controls provide a number of mechanisms that let you define new control types through markup. You can specify new defaults for the subclass controls (these act in much the same way as setting initial property values on a prototype in JavaScript). You can also create parameterized controls in markup - a feature that is very useful for creating complex appearances.

Simple example: setting property defaults

Here is an example of a custom Button control with a label "Next", which, when clicked, bubbles the nextCard event:

    <j:control name="NextButton" base="j:Button">       
        <j:defaults>
            <j:text>Next</j:text>Next
            <j:bubbleEvent>nextCard</j:bubbleEvent>
        </j:defaults>
    </j:control>     

On the page, you can now write:

    <app:NextButton />

Here we're simply using the defaults mechanism to make a shorthand - a button with a predefined label and event property.

On the page, you could have achieved similar results using:

    <j:Button text="Next" bubbleEvent="nextCard" />

(One important difference is that <j:control> generates a new JavaScript class, which you can then reference in script, i.e. you can write new NextButton() in script to create a new instance).

Any property available on the control may be specified in defaults, not just text or bubbleEvent. The basic pattern is:

    <j:control name="BarneyButton" base="j:Button">       
        <j:defaults>
            <propertyName1>
                value
            </propertyName1>               
            <propertyName2>
                value
            </propertyName2>               
            ...
        </j:defaults>
    </j:control>     

Though if you choose you can specify defaults for simple properties using attributes on the <defaults> element:

    <j:control name="BarneyButton" base="j:Button">       
        <j:defaults style='color:red'/>          
    </j:control>     

is the same as:

    <j:control name="BarneyButton" base="j:Button">       
        <j:defaults>
            <j:style>
                color:red
            </j:style>               
        <j:defaults>
    </j:control>     

Complex properties

In the example below, we specify the default value for childControls on a button:

    <jx:control name="BarneyButton" base="j:Button">       
        <j:defaults>
            <j:childControls>
                <img src="/jitsu/quicktours/images/barney.gif"/> 
            </j:childControls>               
        </j:defaults>
    </jx:control>     

In this example, we are indicating that, when someone places a BarneyButton on a page:

    <app:BarneyButton/>

the BarneyButton's should have a <img> child - i.e. there should be an image within the button. On the page, similar results could be achieved using:

    <j:Button>
        <img src="/jitsu/quicktours/images/barney.gif"/> 
    </j:Button>

This means you can use the <defaults> mechanism to create complete control hierarchies.

Properties

When defining controls, you can also add new properties. Then you can use the defaults mechanism to create bindings to those properties. For example, here is a custom control which displays some text in upper case.

<j:control name="UpperText" base="j:Container">

    <j:property name="text" type="string"/>

    <j:defaults>
        <j:childControls>
            <j:Label text="#bind(text, convert:val.toUpperCase())"/>
        </j:childControls>
    </j:defaults>
</j:control>               

In this example, we are using data binding to get the nested label to show the value of the text set on the control. So on the page you can write:

    <app:UpperText text="This should be upper case" />

A note on HTML tags

In the example above, UpperText is based on Container, which by default renders in HTML as a <div>. And Label by default renders as a <span>. So on the page, the final HTML will be:

    <div>
        <span>THIS SHOULD BE UPPER CASE</span>
    </div>

For many controls, you can specify the HTML element the control renders as using the tagName property.

tagName is just like any other property - it can be defaulted in <defaults>, or set explicitly when you use a control. For example, we could define UpperText as:

<j:control name="UpperText" base="j:Container">

    <j:property name="text" type="string"/>

    <j:defaults>
        <j:tagName>li</j:tagName>
        <j:childControls>
            <j:Label tagName="h3"
                 text="#bind(text, convert:val.toUpperCase())"/>
        </j:childControls>
    </j:defaults>
</j:control>

Now, when you use an UpperText on a page it renders as:

    <li>
        <h3>THIS SHOULD BE UPPER CASE</h3>
    </li>

Parameters

A drawback of using #bind bindings is that bindings are not free - it takes time and storage at runtime to wire up bindings. On web browsers, which have limited performance to start with, bindings can quickly become a bottleneck (We've found that more than 300 bindings per page becomes a performance problem).

In many cases, once you've created a control on the page, many of its properties don't change for the rest of the control's lifetime. For that situation, you can use parameters instead of properties when setting up the controls defaults.

You can think of parameters as being like restricted set-once properties - which are used only during control construction. Parameter values are substituted into the defaults as the control is constructed - after that any changes to parameters are ignored.

Here's a (slightly contrived) example that uses a parameterized control to wrap some text in a <b> tag:

<j:control name="BoldText" base="j:Container">

    <j:param name="text" type="string"/>

    <j:defaults>
        <j:childControls>
            <b> <j:Label text="#param(text)"/> </b>
         j:childControls>
    </j:defaults>
</j:control>               

As you can see, using params is very much like using properties. (Like #bind, #param lets you specify converters and other binding expressions).

In the markup above, we are adding a parameter to the control called "text", and then passing text parameter in as the value of the Label child's text property. So when you use this control on the page:

    <app:BoldText text="Foo Bar"/>

At runtime, the page will contain:

    <div>
        <b>
            Foo Bar
        </b>
    </div>

A major advantage of #param is that a fair amount of #param substitution can occur at compile time, making them efficient. However, params are less flexible and more restrictive than full properties. For example, the following will cause a compile-time exception:

    <!-- throws a compile error -->
    <app:BoldText text="#bind(anotherField)"/>

Here we are trying to add a two-way synchronous binding to text. But we declared text as a param, and params can only be used once when the control is created - they don't support changes at runtime. Any changes to anotherField at runtime would be ignored by the BoldText, so the compiler signals an error to alert you to this.

Mixing parameters and properties

#param and #bind can be used in a range of binding expressions, for example you could even mix parameters and properties: :

<j:control name="SuffixLabel" base="j:Label">

    <j:property name="value" type="string"/>

    <j:param name="suffixParam" type="string"/>

    <j:defaults>
        <j:text>#bind(value) + ' ' + #param(suffixParam)</j:text>
    </j:defaults>

</j:control>               

Here we are making a custom label which has two additional attributes: value and suffixParam. The text of the label shows the value and suffix concatenated together.

Since value is a full property, it can be used in further data binding expressions. suffixParam is a parameter, so it cannot. e.g. this is valid:

    <app:SuffixLabel value="#bind(item.age)" suffixParam="Years" />

but this is not:

    <!-- invalid -->
    <app:SuffixLabel value="#bind(item.age)" suffixParam="#bind(another)" />

Placeholders

One of the coolest uses of params is with Placeholders. Consider this example:

<j:control name="FancyPanel" base="j:Container">

    <j:param name="content" type="mixed[]" inline="true" />

    <j:defaults>

        <j:childControls>
            <frameset>
                <legend>Fancy!</legend>
                <j:Placeholder useParam="content"/>
            </frameset>
        </j:childControls>

    </j:defaults>
</j:control>                

First, we added a param called content:

    <j:param name="content" type="mixed[]" inline="true" />

This uses a bit of Jitsu data language magic (see Data Language for more details).

We are specifying that the content parameter can have any kind of Xml content ("mixed[]"), including text, XHTML, or Xml controls. Because its "inline", this means that the content appears directly within the FancyPanel element, without any wrapping markup. In other words, anything you write within the FancyPanel element on the page will be added to the content param.

Then there is the placeholder:

       <j:Placeholder useParam="content"/>

This tells the compiler "when you get to this point creating the children, insert the value of the content parameter.

In other words, when you use the FancyPanel on the page:

    <app:FancyPanel>
        <div>
            Some content with a 
            <j:Button text="Button"/>
        <div>
    </app:FancyPanel>

In the final rendered output you will see:

    <frameset>
        <legend>Fancy!</legend>
        <div>
            Some content with a 
            <input type="button" value="Button"/>
        <div>
    </frameset>

The Placeholder has been replaced by the content of the control instance on the page.

Rounded Panel

You can use parameters to create efficient ways of building complex markup.

Here is a final example that shows how to build a Rounded Panel with a title, combining some of the features shown here:

<j:control name="RoundedPanel" base="j:Container">
    <j:param name="text" type="string"/>
    <j:param name="content" type="mixed[]" inline="true" />

    <j:defaults>
        <j:childControls>
            <!-- this markup derived from from 
                    http://www.albin.net/CSS/roundedCorners/ -->
            <div class="contentWrapper">
                <div class="corners">                    
                    <img class="borderTL" 
                            src="/jitsu/quicktours/images/corner_tl.gif" 
                            alt=" " width="14" height="14" />
                    <img class="borderTR" 
                            src="/jitsu/quicktours/images/corner_tr.gif" 
                            alt=" " width="14" height="14" />
                    <div class="content"> 
                        <!-- header and content here -->
                        <h1><j:Label text="#param(text)"/></h1>
                        <j:Placeholder useParam="content"/>
                    </div><!-- //content -->
                </div><!-- //corners -->
                <div class="bottomCorners">
                    <img class="borderBL" 
                            src="/jitsu/quicktours/images/corner_bl.gif" 
                            alt=" " width="14" height="14" />
                    <img class="borderBR" 
                            src="/jitsu/quicktours/images/corner_br.gif" 
                            alt=" " width="14" height="14" />
                </div>
            </div><!-- //contentWrapper -->
        </j:childControls>

    </j:defaults>
</j:control>                

Here is a usage of the control defined above:

        <app:RoundedPanel text="First panel" style="width: 200px">
        
            <div>Here is some content. This text appears in a 
            rounded corner box.</div>
            
        </app:RoundedPanel>

To be more explicit, we could have written this as:

        <app:RoundedPanel text="First panel" style="width: 200px">
            <app:content>
                <div>Here is some content. This text appears in a 
                rounded corner box.</div>
            </app:content>
        </app:RoundedPanel>

Here' we are explicitly specifying that the <div> and its children belong to the <content> of the RoundedPanel. This explicit form is always valid - the more compact inline version is really just shorthand. While more verbose, the explicit form is required when a control has more than one Placeholder.

Default Values for Parameters

When declaring new parameters and properties, there are several ways to specify the initial defaults.

You can specify the default value when declaring the param or property:

    <j:control name="MyLabel" base="j:Container">
        <j:property name="text" type="string" defaultValue="Hello"/>
    </j:control>

(Subclasses of MyLabel could override this default value using the <defaults> mechanism we showed at the start of this document).

For clearer markup, you can also specify default values on the #bind or #param reference, e.g.

    <j:control name="MyLabel" base="j:Container">
        <j:param name="text" type="string"/>

        <j:defaults>
            <j:childControls>                
                <h1>
                    <j:Label text="#param(text,defaultValue:'Nobody set me')"/>
                </h1>
            </j:childControls>                
        </j:defaults>

    </j:control>                

Finally, you can define default markup within a placeholder:

<j:control name="MyContainer" base="j:Container">
    <j:param name="content" type="mixed[]" inline="true" />
    <j:defaults>
        <j:childControls>                
            <j:Placeholder useParam="content">
                <j:defaultValue>
                    <span><b>Put some content here, please</b></span>
                </j:defaultValue>
            </j:Placeholder>
        <j:childControls>                
    </j:defaults>
</j:control> 
               

Nested Defaults

Sometimes a reusable control benefits from offering multiple layers of defaults, called nested defaults.

For example, the control below renders as a button with a default look, but also gives the page author the option of specifying any different presentation if they want.

    <j:control name="MyButton" base="j:Button">
        <j:param name="text" type="string"/>
        <j:param name="content" type="mixed[]" inline="true"/>

        <j:defaults>
            <j:Placeholder useParam="content">
                <j:defaultValue>
                    <b>
                        <j:Label text="#param(text,defaultValue:'Submit')"/>
                    </b>
                </j:defaultValue>
            </j:Placeholder>
        </j:defaults>

    </j:control>

So if on the page you write:

    <app:MyButton />

the button has a bold label saying "Submit".

You can also write:

    <app:MyButton text="Never submit"/>

To change just the label. Or, you could write:

    <app:MyButton>
       <img src="/images/myimage.gif"/>
    </app:MyButton>

to present the button using an image.