laszlo: Source code of basetree.lzx
<library>
<include href="base/treeselector.lzx" />
<include href="base/basecomponent.lzx" />
<!--==========-->
<!-- BASETREE -->
<!--==========-->
<!--- An abstract base class to build tree controls. -->
<class name="basetree" extends="basecomponent" focusable="false">
<attribute name="defaultplacement" value="children" type="string"/>
<!--- Check to see if this tree is open. Default is false. -->
<attribute name="open" value="false" type="boolean"
setter="_setOpen(open)"/>
<!--- Flag to close other siblings when this tree is open. Default
is false. -->
<attribute name="closesiblings" value="false" type="boolean" />
<!--- Close all immediate children when this tree is closed. Default is
false.-->
<attribute name="closechildren" value="false" type="boolean" />
<!--- Auto scroll if tree is clipped. Default is false. -->
<attribute name="autoscroll" value="false" type="boolean" />
<!--- Check to see if this tree is selected. Default is false, except
for the root of a tree, which its selected attribute is set to
true. -->
<attribute name="selected" value="false" type="boolean"
setter="_setSelected(selected)" />
<!--- Spacing to indent trees on the x-axis. Default is 10. -->
<attribute name="xindent" value="10" type="number" />
<!--- Spacing to indent trees on the y-axis. Default is 20. -->
<attribute name="yindent" value="20" type="number" />
<!--- Meaningful only with data replication. If true, it will
recursively follow the datapath's children. Default is true.
@keywords final -->
<attribute name="recurse" value="true" type="boolean" />
<!--- Meaningful only in root tree. If false, the root item is invisible
and its children are displayed. Default is true.
@keywords final -->
<attribute name="showroot" value="true" type="boolean" />
<!--- Meaningful only in root. Whether to multiselect items. Default is
false.
@keywords final -->
<attribute name="multiselect" value="false" type="boolean" />
<!--- Meaningful only in root. Flag to toggle selected nodes. Default is
false.
@keywords final -->
<attribute name="toggleselected" value="false" type="boolean" />
<!--- Meaningful only in root. Flag to select a tree on focus. Default
is false.
@keywords final -->
<attribute name="focusselect" value="false" type="boolean" />
<!--- If true, this tree is focused. Default is false.
@keywords readonly -->
<attribute name="focused" value="false" type="boolean" />
<!--- Meaningful only in root. If focusselect is false and focusoverlay
is true, then focus has a visual bracket overlay over the focused
tree. Default is false. -->
<attribute name="focusoverlay" value="false" type="boolean" />
<!--- Layout for children. Default is "class: simplelayout; axis: y;
spacing: 0". -->
<attribute name="layout" value="class: simplelayout; axis: y; spacing: 0" />
<!--- If true, this basetree is a leaf node. Default is false.-->
<attribute name="isleaf" value="false" type="boolean"
setter="_setIsLeaf(isleaf)" />
<!--- Child subview number selected. Default to the first one. This will
get set when key press moves up and down.
@keywords private -->
<attribute name="_currentChild" value="0" type="number" />
<!--- The last focused tree. Only available in the root.
@keywords private -->
<attribute name="_lastfocused" value="null" type="boolean" />
<!--- The selection manager. Only available in the root.
@keywords private -->
<attribute name="_selector" value="null" type="expression" />
<!--- This event gets triggered whenever this tree is open. The open
value of this tree is also sent. -->
<attribute name="onopen" value="null" />
<!--- This event gets triggered whenever this tree is selected. The
value of the selection (true or false) is sent with this
event.
Note the args of this has changed from the previous release. -->
<attribute name="onselected" value="null" />
<!--- This event gets triggered whenever this tree is selected. This
tree is sent with the event. The tree root also receives this
event. -->
<attribute name="onselect" value="null" />
<!--- This event gets triggered whenever this tree is focused. The value
of the focus (true or false) is sent with this event. -->
<attribute name="onfocused" value="null" />
<!--- This event gets triggered whenever the tree's focus is
changed. This tree is sent with the event. The tree root also
receives this event.-->
<attribute name="onfocus" value="null" />
<!--- @keywords private : reference to the defaultplacement view
by default this is 'children' but it could be modified by
subclass -->
<attribute name="_children" value="null"/>
<!--- @keywords private -->
<method name="init">
<![CDATA[
if (this.datapath) {
createChildTrees();
}
super.init();
if ( this._children == null ) {
this._children = this.searchSubnodes( "name" , this.defaultplacement );
}
if (this.isRoot()) {
var focusItem = this.item;
if ( ! this.showroot ) {
this.item.destroy();
this.item = null;
this.setAttribute("open", true);
this.children.setAttribute("x", 0);
this.children.setAttribute("y", 0);
var sv = this.children.subviews;
if (sv && sv[0].instanceOf(basetree)) {
focusItem = sv[0].item;
}
}
this._selector = new treeselector(this,
{ multiselect: this.multiselect,
toggle: this.toggleselected });
// Call this only after selector is created
this.changeFocus(focusItem.parent);
}
// Make sure selector knows about me being selected
if (this.selected) this._setSelected(true);
]]>
</method>
<!--- Method to recurse and create subtrees when replicating data.
@keywords private -->
<method name="createChildTrees">
<![CDATA[
var count = this.datapath.getNodeCount();
// Since text nodes 'count', skip if has text and only one
if (this.datapath.getNodeText() != "" && count == 1 || count == 0) {
return;
}
// Don't recurse if we have children.
if (this.children.subviews) {
this.recurse = false;
}
if (! this.recurse) return;
// Replication manager overrides clone's _instanceAttrs, so we have
// to redo them here.
var args = {};
for (var a in this._instanceAttrs) {
if (a == 'id') continue;
if (a == 'showroot') continue; // skip for non-root trees
args[a] = this._instanceAttrs[a]
}
// Check to see if we have a datapath. Most likely, the clone won't
// have a datapath, so use clone manager's datapath.
if (this.datapath['xpath'] != null) {
args.datapath = this.datapath.xpath;
} else if (this.clonemanager.xpath != null) {
args.datapath = this.clonemanager.xpath;
} else {
// couldn't find xpath for recursion
return;
}
var c = this.getChildClass();
if (c != null) {
new global[c](this, args, null, true);
}
]]>
</method>
<!--- Setter for open attribute. Leaf nodes are always closed.
@param boolean o: if true, this tree is open
@keywords private -->
<method name="_setOpen" args="o">
<![CDATA[
if (_initcomplete && this.isleaf) {
this.open = false;
return;
}
if (this.closesiblings && ! this.isRoot()) {
var siblings = parent.children.subviews;
if (siblings['length'] != null) {
for (var i=0; i < siblings.length; i++) {
// .open may not have been created the first
// time through this loop
if (siblings[i]['open'] && siblings[i] != this) {
siblings[i].setAttribute("open", false);
}
}
}
}
// Do this because datapaths only evaluate to strings
if (!_initcomplete && typeof(o) == "string") {
o = (o == "true" );
} else if (o == null) {
o = false;
}
this.open = o;
if (!_initcomplete) return;
if ( ! this.isRoot() ) {
// Close other siblings.
if (this.closesiblings) {
var siblings = parent._children.subviews;
for (var i=0; i < siblings.length; i++) {
if (siblings[i].open && siblings[i] != this) {
// don't want to re-enter this routine
siblings[i].open = false;
siblings[i].openChildren(false);
if (siblings[i].onopen) {
siblings[i].onopen.sendEvent(false);
}
}
}
}
}
var sv = this._children.subviews;
if (this.closechildren && sv) {
for (var i=0; i < sv.length; i++) {
if (sv[i].open) {
sv[i].setAttribute("open", false);
}
}
}
openChildren(o);
if (this.onopen) this.onopen.sendEvent(o);
]]>
</method>
<!--- Calls selector to select this tree.
@param boolean s: whether or not this tree is selected
@keywords private -->
<method name="_setSelected" args="s">
<![CDATA[
// Add tree to selector
if (_initcomplete) {
var r = this.getRoot();
if (s) {
r._selector.select(this);
} else {
r._selector.unselect(this);
}
} else {
// Do this because datapaths only evaluate to strings
if (typeof(s) == "string") {
s = (s == "true" );
}
this.setSelected(s);
}
]]>
</method>
<!--- Setter for isleaf attribute.
@param boolean leaf: if true, this tree is a leaf.
@keywords private -->
<method name="_setIsLeaf" args="leaf">
// do this because datapaths only evaluate to strings
if (typeof(leaf) == "string") {
leaf = (leaf == "true" );
}
this.isleaf = leaf;
</method>
<!--- Returns class to use for instantiating replicated tree children.
If tree is leaf, return null, since we don't care to instantiate
any more subtrees. Override this method to instantiate different
classes. -->
<method name="getChildClass">
if (this.isleaf) return null;
return this.classname;
</method>
<!--- Check to see if this is the root of the tree.
@return Boolean: true if this tree is the root, otherwise
false. -->
<method name="isRoot">
return ! parent.instanceOf(basetree);
</method>
<!--- Get the root of this tree.
@return basetree: the root of this tree. -->
<method name="getRoot">
var v = this;
var p = v.parent;
while (p.instanceOf(basetree)) {
v = v.parent;
p = v.parent;
}
return v;
</method>
<!--- Called when tree is selected using keyboard. Default
action is to select the tree. -->
<method name="keySelect">
this.setAttribute("selected", true);
</method>
<!--- Get current tree selection.
@return Object: if multiselect is true, an array of basetrees,
else the selected basetree. If none selected, returns null. -->
<method name="getSelection">
var root = this.getRoot() ;
var selection = root._selector.getSelection();
if (root._selector.multiselect) {
return selection;
} else if (selection.length == 0) {
return null;
} else {
return selection[0];
}
</method>
<!--- Called by selectionmanager when this is selected or
unselected.
@param Boolean s: whether tree is selected or not.
@keywords private -->
<method name="setSelected" args="s">
<![CDATA[
this.selected = s;
var root = this.getRoot();
if (root.onselect) root.onselect.sendEvent(this);
if (this != root && this.onselect) this.onselect.sendEvent(this);
if (this.onselected) this.onselected.sendEvent(s);
]]>
</method>
<!--- Change the focus to new tree and unfocus the previous focused
tree. If the focusselect for the tree is true, this method will also
select the focused tree.
@param Basetree focusedTree: the tree to focus. If null, the
current tree is focused. -->
<method name="changeFocus" args="focusedTree">
<![CDATA[
if (focusedTree == null) focusedTree = this;
var ftRoot = focusedTree.getRoot();
// Remove last focused item's focus
if (ftRoot._lastfocused) {
ftRoot._lastfocused.setTreeFocus(false,ftRoot);
}
// Set correct _currentChild settings.
if (focusedTree != ftRoot) {
var index = focusedTree.parent.getChildIndex(focusedTree);
if (index != -1) {
focusedTree.parent.setAttribute("_currentChild", index);
}
}
// See lastfocus to new focused tree
focusedTree.setTreeFocus(true,ftRoot);
// If focusselect, don't use focusoverlay.
var useFocusOverlay = ftRoot.focusoverlay;
if (ftRoot.focusselect) {
useFocusOverlay = false;
}
LzFocus.setFocus(focusedTree.item, useFocusOverlay);
ftRoot.setAttribute("_lastfocused", focusedTree);
if (ftRoot.focusselect) focusedTree.setAttribute("selected", true);
if (ftRoot.autoscroll) focusedTree.doAutoScroll(ftRoot);
]]>
</method>
<!--- Autoscroll if this tree is outside of scroll view.
@param Basetree root: the root of this tree.
@keywords private -->
<method name="doAutoScroll" args="root">
<![CDATA[
if (root.height > root.parent.height) {
var relY = this.getAttributeRelative('y', root);
if (relY < 0) {
root.setAttribute('y', root.y - relY);
return;
}
var delta = root.parent.height - relY - this.item.height;
if (delta < 0) {
root.setAttribute('y', root.y + delta);
}
}
]]>
</method>
<!--- Set the focus of this tree. This will not onfocus the last focused
tree. Use changeFocus() for to do that.
@param Boolean focus: true you want the tree focused, else false.
@keywords private
-->
<method name="setTreeFocus" args="focus,root">
<![CDATA[
this.item.setAttribute("focusable", focus);
this.setAttribute("focused", focus);
if (root == null) root = this.getRoot();
if (root.onfocus) root.onfocus.sendEvent(this);
if (this != root && this.onfocus) this.onfocus.sendEvent(this);
//if (this.onfocused) this.onfocused.sendEvent(focus);
]]>
</method>
<!--- Get the child index of the child passed in.
@param LzView child: a child view of the current tree.
@return Number: the child index of the view. If not a child,
returns -1. -->
<method name="getChildIndex" args="child">
<![CDATA[
if (children.subviews != null) {
for (var i=0; i < children.subviews.length; i++) {
if (children.subviews[i] == child) {
return i;
}
}
}
return -1;
]]>
</method>
<!--- Keyboard focus on parent. If no parent exists, keep focus on
current tree.
@keywords private -->
<method name="_focusParent">
<![CDATA[
// Make sure there's a parent to select.
if (this.isRoot() || parent.item == null) return;
this.setAttribute("_currentChild", 0);
this.changeFocus(parent);
]]>
</method>
<!--- Keyboard focus on first child. If none exists, keep focus on
current tree.
@keywords private -->
<method name="_focusFirstChild">
<![CDATA[
var n = 0;
if (children.subviews &&
children.subviews[n].instanceOf(basetree)) {
this.setAttribute("_currentChild", n);
this.changeFocus(children.subviews[n]);
}
]]>
</method>
<!--- Keyboard focus on last child. If none exist, keep focus on current
tree.
@keywords private -->
<method name="_focusLastChild">
<![CDATA[
var n = children.subviews.length - 1;
if (children.subviews &&
children.subviews[n].instanceOf(basetree)) {
var last = children.subviews[n];
if (last.open && last.children.subviews) {
var next = last.children.subviews.length -1;
if (last.children.subviews[next].instanceOf(basetree)) {
last._focusLastChild();
return;
}
}
this.setAttribute("_currentChild", n);
this.changeFocus(children.subviews[n]);
}
]]>
</method>
<!--- Keyboard focus on previous sibling. If we're the first sibling,
calls _focusParent(). If none exists, keep focus on current
tree.
@keywords private -->
<method name="_focusPreviousSibling">
<![CDATA[
// Make sure we're not root
if (this.isRoot()) return;
// if we're the first sibling, previous goes to parent
if (parent._currentChild == 0) {
this. _focusParent();
return;
}
var prev = parent._currentChild - 1;
parent.setAttribute("_currentChild", prev);
// If previous sibling is open, select last child of that sibling
var sibling = parent.children.subviews[prev]
if (sibling.open && sibling.children.subviews &&
sibling.children.subviews[0].instanceOf(basetree)) {
sibling._focusLastChild();
} else {
this.changeFocus(sibling);
}
]]>
</method>
<!--- Keyboard focus on next sibling. If we're not root, focus on
parent's next sibling, else keep focus on current tree.
@keywords private -->
<method name="_focusNextSibling">
<![CDATA[
// Make sure we're not root
if (this.isRoot()) return;
var next = parent._currentChild + 1;
if (next < parent.children.subviews.length) {
parent.setAttribute("_currentChild", next);
this.changeFocus(parent.children.subviews[next]);
} else if (! this.isRoot()) {
parent._focusNextSibling();
}
]]>
</method>
<!--- @keyword private
Map keyboard navigation for tree.
Space (32): call keySelect().
Left (37): close tree if open, else focus on parent.
Up (38): focus on previous sibling.
Right (39): open tree if closed, else focus on first child.
Down (40): focus on first child if open, else focus next sibling.
-->
<method name="keyboardNavigate" args="kc">
<![CDATA[
if (kc == 32) { // space
this.keySelect();
} else if (kc == 37) { // left
if (this.open) {
this.setAttribute("open", false);
} else {
this._focusParent();
}
} else if (kc == 38) { // up
this._focusPreviousSibling();
} else if (kc == 39) { // right
if (! this.open) {
this.setAttribute("open", true);
} else {
this._focusFirstChild();
}
} else if (kc == 40) { // down
if (this.open &&
this.children.subviews &&
this.children.subviews[0].instanceOf(basetree)) {
this._focusFirstChild();
} else {
this._focusNextSibling();
}
}
]]>
</method>
<!-- View to place basetree node item. This is where the visual
component of the tree goes. -->
<view name="item">
<!--- @keywords private -->
<method event="onkeydown" args="kc">
classroot.keyboardNavigate(kc);
</method>
</view>
<method name="openChildren" args="o"> <![CDATA[
var makevisible = ( o && children.subviews != null );
children.setVisible(makevisible);
]]>
</method>
<!-- View to place child trees. -->
<view name="children" x="${parent.xindent}" y="${parent.yindent}">
<method name="init"> <![CDATA[
setVisible(classroot.open && this.subviews != null );
super.init();
]]>
</method>
<method event="onaddsubview">
<![CDATA[
// If this is the first one added, send event.
if (this.subviews.length == 1 && classroot.open) {
setAttribute("visible", true);
}
]]>
</method>
</view>
</class> <!-- basetree -->
</library>
<!-- -->
<!-- Copyright 2002-2004 Laszlo Systems, Inc. All Rights Reserved. -->
<!-- Unauthorized use, duplication or distribution is strictly prohibited. -->
<!-- This software is the proprietary information of Laszlo Systems, Inc. -->
<!-- Use is subject to license terms. -->
<!-- -->
<!-- -->
<!-- Laszlo Presentation Server version 2.2.1 lps-2.2.1-001185-0001 -->
<!-- -->