Selectionmanager and multi/range-select

For a long time, I’ve wondered why the OpenLaszlo selectionmanager has such a bizarre API for enabling/disabling range- and multi-selection.  A quick introduction: the selectionmanager is a helper class that manages selection between a group of views. It’s very handy for building custom lists. Range-selection means shift-clicking to select a group of items. Multi-selection means control-clicking to build up a selection. Both range- and multi-selection are enabled by default.

I would expect the API to have an on/off metaphor, e.g. israngeselect=”false”. Instead, you override the isRangeSelect() and/or isMultiSelect() methods at the instance level, e.g.

<selectionmanager name="selman">
    <method name="isRangeSelect">
        return false;
    </method>

    <method name="isMultiSelect" args="selectedView">
        return false;
    </method>
</selectionmanager>

During a training class, I wondered about this aloud, and one of my students (Chuq) suggested that it may be to allow developers to determine – each time a selection is made – whether to allow multi-/range-selection for that particular click.

An example use-case may be that you have a list of groceries, and you only want the user to be able to select one of each type of grocery (e.g. fruits, vegetables, etc.). So with full credit to Chuq for the idea, I gave it a try:

And here's the source:

<canvas proxied="false" width="300" height="260">
    <dataset name="ds">
        <groceries>
            <item name="apple" type="fruit" />
            <item name="orange" type="fruit" />
            <item name="banana" type="fruit" />
            <item name="carrot" type="vegetable" />
            <item name="potato" type="vegetable" />
            <item name="celery" type="vegetable" />
            <item name="pasta" type="bulk" />
            <item name="rice" type="bulk" />
            <item name="beans" type="bulk" />
        </groceries>
    </dataset>

    <alert name="errorAlert" width="250">
        You can only select one of each type of grocery 
        (i.e. fruit, vegetable, bulk).
    </alert>

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

    <text width="300" multiline="true">
        Control-click to select one of each type of grocery from the list
        below:
    </text>
 
    <view name="grocerylist" width="300">
        <simplelayout spacing="1" />

        <selectionmanager name="selman">
            <method name="isRangeSelect">
                return false;
            </method>

            <method name="isMultiSelect" args="selectedView">
                var currentSelection = getSelection();
                for (var i in currentSelection) {
                    var item = currentSelection[i];
                    if (item.datapath.p.getAttr("type") == selectedView.datapath.p.getAttr("type")) {
                        errorAlert.open();
                        return false;
                    }
                }
                return super.isMultiSelect();
            </method>
        </selectionmanager>

        <replicator dataset="ds" xpath="groceries/item">
            <view bgcolor="0xeaeaea" width="100%">
                <method name="setSelected" args="isSel">
                    var bgc = isSel ? yellow : 0xeaeaea;
                    setAttribute("bgcolor", bgc);
                </method>
                <handler name="onmousedown">
                    parent.selman.select(this);
                </handler>
                <text datapath="@name" fontsize="16" />
                <text datapath="@type" fontsize="16" fontstyle="italic" 
                      align="right" />
            </view>
        </replicator>
    </view>

</canvas>

Using the Value Attribute

Whether you realize it or not, you often use a subclass of the basevaluecomponent in OpenLaszlo. A typical example is radiobutton (which is used with radiogroup). The example for radiogroup in the OpenLaszlo reference is pretty simple:

<canvas debug="true">
    <radiogroup id="group1">
        <radiobutton value="1" text="one"/>
        <radiobutton value="2" text="two" selected="true"/>
        <radiobutton value="3" text="three"/>
    </radiogroup>
</canvas>

I modified it a little, to include the debugger, but note that the values for the radiobuttons are “1”, “2” and “3”. If you decide that you need to use values such as “one”, “two” and “three” instead of numbers, the first thing you might try is something like this:

<!-- WARNING: BAD EXAMPLE -->
<canvas debug="true">
    <radiogroup id="group1">
        <radiobutton value="one" text="one"/>
        <radiobutton value="two" text="two" selected="true"/>
        <radiobutton value="three" text="three"/>
    </radiogroup>
</canvas>

However, that will lead to debugger warnings along the lines of:

ERROR @filename.lzx#5: reference to undefined variable ‘three’
ERROR @filename.lzx#4: reference to undefined variable ‘two’
ERROR @filename.lzx#3: reference to undefined variable ‘one’

The issue is that the value attribute is of type expression. That means it has to be a valid JavaScript expression. Think of what you can put on the right-hand side of an equals sign in JavaScript. If you put the number 1 (I’ve omitted the quotes here intentionally), you’ll assign the number 1. If you put the characters one (again, quotes omitted intentionally), then the runtime will search for a variable with the name “one”.

What you really want is the string “one”, so you need to quote it. The double quotes are the XML attribute quotes. Since in JavaScript, you can alternate between single- and double-quotes freely, the easiest solution is to use single-quotes within the double-quotes, to specify a string. e.g. value=”‘one'”. The above example would then look like:

<!-- CORRECT -->
<canvas debug="true">
    <radiogroup id="group1">
        <radiobutton value="'one'" text="one"/>
        <radiobutton value="'two'" text="two" selected="true"/>
        <radiobutton value="'three'" text="three"/>
    </radiogroup>
</canvas>