The Working Class

* - *
|

I've been critical of classic, class-based OOP in this forum, so I decided it was time to point out a place where I think it works really well in Laszlo. I really don't like iterators in Java -- I think they're cumbersome, and it irritates me that I need a whole object just to represent what is essentially a counter. However, a layout in Laszlo is an iterator that works well as an object, because it has meaningful state.

A layout tends to control its siblings. Even novice Laszlo users are familiar with a construct like this:

<canvas>
    <class name="box" width="100" height="10"/>
    <box bgcolor="red"/>
    <box bgcolor="blue"/>
    <box bgcolor="green"/>
    <simplelayout name="myLayout" spacing="2"/>
</canvas>

(EDIT PROGRAM)

Here myLayout operates on the colored boxes that are its siblings. One thing that's nifty about layouts is that they can be active -- they can be written to respond to changes in the attributes that affect the layout. Simplelayout, for instance, knows that it is affected by changes to the size of a view in the layout axis. The layout in the example below responds as the boxes change height when they are clicked.

<canvas>
    <class name="box" width="100" height="10"
           onclick="this.animate( 'height' , 10 , 300 , true )"/>
    <box bgcolor="yellow"/>
    <box bgcolor="teal"/>
    <box bgcolor="maroon"/>
    <simplelayout name="myLayout" spacing="2"/>
</canvas>

(EDIT PROGRAM)

Simplelayout is aptly named: it has fairly simple behaviors, and if it didn't have a few important optimizations; it'd be less than 30 lines. A layout has several useful members that are provided by the base class:

  • An abstract update routine
  • This is the meat of the layout.

  • A subviews array
  • The list of views controlled by the layout.

  • A handy API
  • That makes it easy to reorder or change the subviews array.

So using the basic features of layout, I can reconstruct simplelayout in about 10 lines of code (and demonstrate my familiarity with the more obtuse CSS colors).

<canvas>
    <class name="box" width="100" height="10"/>
    <box name="box1" bgcolor="silver"/>
    <box name="box2" bgcolor="fuchsia"/>
    <box bgcolor="navy"/>

    <button onclick="with( parent ) aLayout.swapSubviewOrder( box1, box2 );">
        Swap
    </button>

    <layout name="aLayout">
        <attribute name="spacing" value="2"/>
        <method name="update">
            <![CDATA[
            var pos = 0;
            for ( var i = 0; i < subviews.length; i++ ){
                subviews[ i ].setY ( pos );
                pos += subviews[ i ].height + spacing;
            }
            ]]>
        </method>
    </layout>
</canvas>

(EDIT PROGRAM)

Note that all of the heavy lifting of registering added subviews and calling the update routine is done for me -- I just have to add the logic that knows what do when the layout updates.

From this, it should be clear that layouts do not have to be solely position related, and multiple layouts can apply to a given view. Here's a layout that sets the background colors of the views it controls:

<canvas>
    <class name="box" width="100" height="10"/>
    <box/> <box/> <box/> <box/> <box/> <box/> <box/> <box/>

    <simplelayout/>

    <layout>
        <attribute name="colorIncrement" value="0x202020"/>
        <method name="update">
            <![CDATA[
            var color = 0;
            for ( var i = 0; i < subviews.length; i++ ){
                subviews[ i ].setBGColor ( color );
                color += colorIncrement;
            }
            ]]>
        </method>
    </layout>
</canvas>

(EDIT PROGRAM)

Some people are surprised that layouts are themselves objects. This was, in fact, a design choice that was made during the development of language. Laszlo provides some syntactic sugar to allow developers to specify a layout as an attribute (<view layout="class: 'wrappinglayout'">) but heavy users of LZX understand that by making layouts share the API of the Laszlo base class (LzNode), and by giving them access to such features as constraints and events, layouts can be much more abstract and powerful.

For instance, here's a layout that extends simplelayout to make it fill its container with instances of a class that is passed to it as an attribute.

<canvas>
    <class name="box" width="100" height="10" bgcolor="red"/>

    <class name="fillLayout" extends="simplelayout">
        <attribute name="fillClass" when="once"/>

        <method name="update">
            <![CDATA[
            super.update();
            var lastsub = subviews[ subviews.length - 1 ];
            while( !lastsub || lastsub. y < immediateparent.height ) {
                var lastsub = new fillClass( immediateparent );
            }
            ]]>
        </method>
    </class>

    <window height="200" width="100" resizable="true">
        <fillLayout fillClass="box" spacing="5"/>
    </window>
</canvas>

(EDIT PROGRAM)

This is almost embarassingly easy to write, and you should experiment with editing the source to make the fillLayout create, say, buttons instead of boxes. If you play with this, though, you'll note that if you resize the window, the layout doesn't create new views. This is because the layout has not registered its dependency on the size of its immediate parent. Lucky for us, the layout base class comes with an updateDelegate, which is a callback that can be registered with events that, when sent, make the layout run its update routine. All we have to do is register this callback with the onheight event of the immediate parent, like so:

    ...
    <class name="fillLayout" extends="simplelayout">
        <method event="oninit">
            updateDelegate.register( immediateparent , 'onheight' );
        </method>
    ...

(EDIT PROGRAM)

and now the layout responds as the window resizes.

Hopefully, you're now convinced that layouts are fairly easy to write. Experience has also shown that layouts tend to be a useful optimization tool. Some relationships are just easier to express in a for loop than in a web of declarative code, and layout provides a useful bridge between the worlds of declarative and procedural coding.

Ultimately, I think the layout base class works well for a few reasons:

  1. it's abstract
  2. A layout does not try to model user-land; a layout tries to model developer-land. It makes sense for a tool used by a developer to have a constrained and well defined API; it doesn't make that much sense for a user. It's interesting that a relatively complex thing like fillLayout (above) is much shorter than the extensions made to the basebutton class by Laszlo's concrete button class. OOP is poorly suited to the idiosyncratic and somewhat whimsical demands of user-experience.

  3. it's a container
  4. I think OOP is best suited to containers. This is the essence of the C++ STL, and it makes it all the more ironic that the princess of OOP, Java, has (up 'till now at least) had such problems with genericity. No one wants to have to write a hash map or an indeterminite-length list over and over (although it seems like in most languages, we still do.)

  5. it's unambitious
  6. Essentially, a layout is just an array of items -- wrapped with some useful methods and hooked into the Laszlo runtime instance hierarchy in useful ways. Layouts don't try to proscribe what a developer can do with them, nor do they make any assumptions about what purpose they ultimately serve.

I wish I could say that classes work this well for more of the Laszlo components, but in general, I think class-based OOP is bad for UI programming.

|
* * *