layout.lzx

<library>
<!---
    layout.lzx
   
    @copyright Copyright 2001-2010 Laszlo Systems, Inc.  All Rights Reserved.
               Use is subject to license terms.
   
    @topic LFC
    @subtopic Controllers
    @affects lzlayout
    @access public
  -->
  
<class name="layout" extends="node">
    <doc>
        <tag name="shortdesc"><text>Abstract layout base class.</text></tag>
        <text>
        <p>A layout is used to automatically distribute, align, or
        otherwise arrange the subviews of the view that contains it (it
        arranges the views that are it's siblings). Using a layout
        obviates the need to precisely position the views of an
        application and allows an application to resize or reconfigure
        gracefully according to the constraints of its container.</p>
    
        <p>The layout class is a null layout — it does not arrange
        it's siblings. It is the base class that other layouts extend.
        <classname>simplelayout</classname> is a basic extension of the
        layout class. The example below illustrates using a simple
        layout to distribute views along the y axis.</p>
    
        <example title="Using a simple layout to arrange views"><programlisting>
        <canvas height="60">
        <view bgcolor="#DDFFFF">
            <text>Without a layout, it can be difficult</text>
            <text x="2" y="5">to manually position your views</text>
        </view>
        <view y="25" bgcolor="#FFDDFF">
            <simplelayout axis="y" />
            <text>A layout makes</text>
            <text>positioning simple and automatic</text>
        </view>
        </canvas>
        </programlisting></example>
    
        <p>Layouts are so important, there is a shorthand for specifying them
        using the <xref linkend="LzView.__ivars__.layout"/> attribute
        of <xref linkend="LzView"/>.  The previous example could be more
        compactly specified:</p>
    
        <example title="Using the layout attribute to specify a layout"><programlisting>
        <canvas height="35">
        <view layout="class: simplelayout; axis: y">
            <text>A layout makes</text>
            <text>positioning simple and automatic</text>
        </view>
        </canvas>
        </programlisting></example>
    
        <p>Because <xref linkend="lz.simplelayout"/> is the default
        layout, the above example could also have used just <code>layout="axis:
        y"</code>.</p>
        
        <p>Layouts, like constraints and animators, affect specific
        attributes of a view. Views can have more than one layout, as long
        as each set of attributes associated with a layout does not
        overlap with any of the other sets.</p>
        </text>
    </doc>
<!--- @access private
 * Just used to effect a type cast of immediateparent to LzView 
 * TODO [max 2010-07] add LzView type back when possible
 -->
  <attribute name="vip"/>

  <!---
     Set to true if layout is locked from updates.
    
     @devnote locked is set to 2 in the prototype of the layout. This
     is a special signal that the layout is locked temporarily until
     it resolves its references
    
     @type Boolean
   -->
  <attribute name="locked" value="2"/>;
  <!---
     Setter for locked
     Unlock the layout once update is done.
    
     @access private
   -->
  <setter name="locked" args="locked ">
      if ( this.locked == locked ) return;
      if ( locked ){
          this.lock();
      } else {
          this.unlock();
      }
  </setter>

  <!---
     Array of the views this layout controls. Used by subclasses of
     <code>layout</code> to implement their arrangement algorithm by
     adjusting the appropriate attribute of each view.
    
     @type [LzView]
     @access public
   -->
  <attribute name="subviews"/>

  <!--- A delegate used to update the layout.
      @type LzDelegate
    -->
  <attribute name="updateDelegate"/>
  
<!---
    @param view: A view above this one that the Layout should apply to. Note:
    this may not ultimately be the same as the view that it will eventually be
    attached to, due to placement.
    @param args: Constructor arguments.
   
    @access private
  -->
<method name="construct" args="view , args">
    // Set locked to special default value of 2 to prevent updates until 
    // __parentInit() is called. Set as early as possible - can't wait for 
    // setter to be called.
    this.locked = 2

    super.construct(view, args);
    this.subviews = [];

    this.vip = (this.immediateparent cast LzView);
    
    // view.layouts: If it doesn't already exist, layouts create an array
    //in the view that it attaches to that hold the list of layouts attached
    //to the view.
    if ( this.vip.layouts == null ){
        this.vip.layouts = [ this ];
    } else {
        this.vip.layouts.push( this );
    }

    this.updateDelegate = new LzDelegate( this , "update" );

    // register for unlock when parent inits, or unlock if already inited
    if (this.immediateparent.isinited) {
        this.__parentInit();
    } else {
        new LzDelegate( this , "__parentInit", this.immediateparent, "oninit" );
    }
</method>

<!---
    @access private
  -->
<handler name="onconstruct">
    //all layouts need to know when their view adds/deletes a subview
    new LzDelegate (  this, "gotNewSubview",
                                        this.vip, "onaddsubview" );
    //this is never called b/c removeSubview is not implemented yet
    new LzDelegate (  this, "removeSubview",
                                    this.vip, "onremovesubview" );

    var vsl = this.vip.subviews.length;

    for (var i = 0; i < vsl; i++){
        this.gotNewSubview( this.vip.subviews[i] );
    }
</handler>

<!---
    @access private
  -->
<method name="destroy">
    if ( this.__LZdeleted ) return;
    this.releaseLayout(true);
    super.destroy();
</method>

<!---
    Reset any held parameters (such as kept sizes that may have changed) and
    before updating.
   
    @access public
   
    @param Any e: The event data that is passed by the delegate that called this
    funciton. This is usually unused, since more than one type of delegate can
    call reset.
  -->
<method name="reset" args="e = null ">
    if ( this.locked ) { return; }
    //defalt behavior on reset is to update
    this.update( e );
</method>

<!---
    Called whenever a new subview is added to the layout. This is called both
    in the layout constructor and when a new subview is called after layout
    has been created. This is only called if the view's "ignorelayout" option is
    not set.  Subclasses may override this method to decide whether or
    not to add a particular view to the <code>subviews</code> array.
   
    @access public
    @param LzView sd: The subview to add.
  -->
<method name="addSubview" args="sd ">
    var layoutAfter = sd.options['layoutAfter'];
    if ( layoutAfter ){
        //this wants to get inserted in a specific place in the layout
        this.__LZinsertAfter( sd , layoutAfter );
    } else {
        this.subviews.push ( sd );
    }
</method>

<!---
    Called whenever the layout discovers a new subview. Checks to see if view
    should be added by inspecting the view's "ignorelayout" option.
   
    @access private
    @param sd: The subview to add.
  -->
<method name="gotNewSubview" args="sd ">
    if ( ! sd.options['ignorelayout'] ){
        this.addSubview( sd );
    }
</method>

<!---
    Called when a subview is removed. This is not well tested.
   
    @access public
    @param LzView sd: The subview to be removed.
  -->
<method name="removeSubview" args="sd ">
    var subviews = this.subviews;
    for ( var i = subviews.length-1; i >= 0; i-- ){
        if ( subviews[ i ] == sd ){
            subviews.splice( i , 1 );
            break;
        }
    }

    this.reset();
</method>

<!---
    Called when a subview is to be ignored by the layout. By default, most
    layouts include all the subviews of a given view.
   
    @access public
    @param LzView s: The subview to ignore.
  -->
<method name="ignore" args="s ">
    //default behavior on addSubview is to reset
    var subviews = this.subviews;
    for (var i = subviews.length - 1; i >= 0; i-- ){
        if ( subviews[ i ] == s ){
            subviews.splice( i , 1 );
            break;
        }
    }
    this.reset();
</method>

<!---
    Lock the layout from processing updates. This allows the layout to register
    for events that it generates itself. Unfortunately, right now all subclass
    routines that can generate calls that may result in the layout calling
    itself should check the lock before processing. Failure to do so is not
    catastrophic, but it will slow down your program.
  -->
<method name="lock">
    this.locked = true;
</method>


<!---
    Unlock the layout once update is done.
   
    @param Any ignore: Ignored. <code>unlock</code> accepts an argument as
    a convenience so that it can be used as an event handler method.
  -->
<method name="unlock" args="ignore=null">
    this.locked = false;
    this.reset()
</method>

<!---
    @access private
  -->
<method name="__parentInit" args="ignore=null">
    // check for special value of 2, set in construct()
    if ( this.locked == 2){
        if (this.isinited) {
            this.unlock();
        } else {
            new LzDelegate( this , "unlock", this, "oninit" );
        }
    }
</method>

<!---
    Remove the layout from the view and unregister the delegates that the layout
    uses.
    @deprecated will be removed from a future release.
  -->
<method name="releaseLayout" args="fromdestroy=null">
    if ($debug) {
        if (fromdestroy == null) Debug.deprecated(this, arguments.callee, this.destroy);
    }
    // if called from destroy(), don't remove delegates
    if (fromdestroy == null && this.__delegates != null) this.removeDelegates();
    if (this.immediateparent && this.vip.layouts) {
        for ( var i = this.vip.layouts.length -1 ; i >= 0 ; i-- ){
            if ( this.vip.layouts[ i ] == this ){
                this.vip.layouts.splice( i , 1 );
            }
        }
    }
</method>

<!---
    Reorder the second subview given  to this function so that it immediately
    follows the first. Doesn't touch the placement of the first subview given
   
    @access public
   
    @param LzView sub1: The reference subview which the second subview should
    follow in the layout order. Alternatively, can be "first" or "last"
    @param LzView sub2: The subview to be moved after the reference subview.
  -->
<method name="setLayoutOrder" args="sub1 , sub2 ">
    var subviews = this.subviews;
    for (var i2 = subviews.length -1; i2 >= 0 ; i2-- ){
        if ( subviews[ i2 ] === sub2 ){
            subviews.splice( i2, 1 );
            break;
        }
    }
    // check if sub2 was found
    if (i2==-1) {
        if ($debug) Debug.warn('second argument for setLayoutOrder() is not a subview')
        return;
    }

    if ( sub1 == "first" ){
        subviews.unshift( sub2 );
    } else if ( sub1 == "last" ){
        subviews.push( sub2 );
    } else {
        for (var i = subviews.length -1; i >= 0 ; i-- ){
            if ( subviews[ i ] === sub1 ){
                subviews.splice( i+1 , 0 , sub2 );
                break;
            }
        }
        // check if sub1 was found
        if (i==-1) {
            if ($debug) {
                if (sub1 == sub2) {
                    Debug.warn('%w is the same as %w', sub1, sub2)
                } else {
                    Debug.warn('first argument for setLayoutOrder() is not a subview')
                }
            }
            // put sub2 back
            subviews.splice(i2, 0, sub2);
        }
    }
    this.reset();
    return;
</method>

<!---
    Swap the positions of the two subviews within the layout
    @access public
   
    @param LzView sub1: The reference subview which the second subview should
    follow in the layout order.
    @param LzView sub2: The subview to be moved after the reference subview.
  -->
<method name="swapSubviewOrder" args="sub1 , sub2 ">
    //this will cause problems if sub1 or sub2 are not subviews
    var s1p = -1;
    var s2p = -1;

    var subviews = this.subviews;
    for (var i = subviews .length -1; i >= 0 && ( s1p < 0 || s2p < 0 );
                   i-- ){
        if ( subviews[ i ] === sub1 ){
            s1p = i;
        } 
        if ( subviews[ i ] === sub2 ){
            s2p = i;
        }
    }

    if (s1p >= 0 && s2p >= 0) {
        subviews[ s2p ] = sub1;
        subviews[ s1p ] = sub2;
    } else if ($debug) {
        Debug.warn('Invalid subviews for swapSubviewOrder()');
    }

    this.reset();
    return;
</method>

<!---
    @access private
  -->
<method name="__LZinsertAfter" args="newsub , oldsub ">
    var subviews = this.subviews;
    for ( var i = subviews.length -1 ; i >= 0; i-- ){
        if ( subviews[ i ] == oldsub ){
            subviews.splice( i , 0 , newsub );
        }
    }
</method>

<!---
    <p>Update is called whenever the layout needs to be updated.</p>
   
    <p>In the <code>layout</code> class, update does nothing.
    Subclasses of layout override this method to implement their
    layout algorithm.  For best performance, any update method should
    check first to see if the layout is <code>locked</code> and return
    immediately if so.  This will avoid redundant updates when a
    layout is in transition.</p>
   
    @param Any e: The event data that caused this update.  Typically
    unused since various events may cause an update.
   
    @access public
  -->
<method name="update" args="e=null">
</method>

<!---
    @access private
  -->
<method name="toString">
    return "lz.layout for view " + this.immediateparent;
</method>

</class>

</library>

Cross References

Classes