<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 --> <!-- -->