Adding mapbox-gl branch
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
Author: arv@google.com (Erik Arvidsson)
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.ui.tree.BaseNode
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.ui.tree.BaseNodeTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,290 @@
|
||||
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.ui.tree.BaseNodeTest');
|
||||
goog.setTestOnly('goog.ui.tree.BaseNodeTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.html.legacyconversions');
|
||||
goog.require('goog.html.testing');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.ui.Component');
|
||||
goog.require('goog.ui.tree.BaseNode');
|
||||
goog.require('goog.ui.tree.TreeControl');
|
||||
goog.require('goog.ui.tree.TreeNode');
|
||||
var stubs = new goog.testing.PropertyReplacer();
|
||||
|
||||
function setUp() {
|
||||
// Reset global flags to their defaults.
|
||||
stubs.set(goog.html.legacyconversions, 'ALLOW_LEGACY_CONVERSIONS', true);
|
||||
}
|
||||
|
||||
function testAdd() {
|
||||
var node1 = new goog.ui.tree.TreeNode('node1');
|
||||
var node2 = new goog.ui.tree.TreeNode('node2');
|
||||
var node3 = new goog.ui.tree.TreeNode('node3');
|
||||
var node4 = new goog.ui.tree.TreeNode('node4');
|
||||
|
||||
assertEquals('node2 added', node2, node1.add(node2));
|
||||
assertEquals('node3 added', node3, node1.add(node3));
|
||||
assertEquals('node4 added', node4, node1.add(node4, node3));
|
||||
|
||||
assertEquals('node1 has 3 children', 3, node1.getChildCount());
|
||||
assertEquals('first child', node2, node1.getChildAt(0));
|
||||
assertEquals('second child', node4, node1.getChildAt(1));
|
||||
assertEquals('third child', node3, node1.getChildAt(2));
|
||||
assertNull('node1 has no parent', node1.getParent());
|
||||
assertEquals('the parent of node2 is node1', node1, node2.getParent());
|
||||
|
||||
assertEquals('node4 moved under node2', node4, node2.add(node4));
|
||||
assertEquals('node1 has 2 children', 2, node1.getChildCount());
|
||||
assertEquals('node2 has 1 child', 1, node2.getChildCount());
|
||||
assertEquals('the child of node2 is node4', node4, node2.getChildAt(0));
|
||||
assertEquals('the parent of node4 is node2', node2, node4.getParent());
|
||||
}
|
||||
|
||||
function testExpandIconAfterAddChild() {
|
||||
var tree = new goog.ui.tree.TreeControl('root');
|
||||
var node1 = new goog.ui.tree.TreeNode('node1');
|
||||
var node2 = new goog.ui.tree.TreeNode('node2');
|
||||
tree.render(goog.dom.createDom('div'));
|
||||
tree.addChild(node1);
|
||||
|
||||
node1.addChild(node2);
|
||||
assertTrue('expand icon of node1 changed to L+', goog.dom.classlist.contains(
|
||||
node1.getExpandIconElement(), 'goog-tree-expand-icon-lplus'));
|
||||
|
||||
node1.removeChild(node2);
|
||||
assertFalse('expand icon of node1 changed back to L',
|
||||
goog.dom.classlist.contains(
|
||||
node1.getExpandIconElement(), 'goog-tree-expand-icon-lplus'));
|
||||
}
|
||||
|
||||
function testExpandEvents() {
|
||||
var n = new goog.ui.tree.BaseNode('');
|
||||
n.getTree = function() {};
|
||||
var expanded = false;
|
||||
n.setExpanded(expanded);
|
||||
assertEquals(expanded, n.getExpanded());
|
||||
var callCount = 0;
|
||||
n.addEventListener(goog.ui.tree.BaseNode.EventType.BEFORE_EXPAND,
|
||||
function(e) {
|
||||
assertEquals(expanded, n.getExpanded());
|
||||
callCount++;
|
||||
});
|
||||
n.addEventListener(goog.ui.tree.BaseNode.EventType.EXPAND,
|
||||
function(e) {
|
||||
assertEquals(!expanded, n.getExpanded());
|
||||
callCount++;
|
||||
});
|
||||
n.setExpanded(!expanded);
|
||||
assertEquals(2, callCount);
|
||||
}
|
||||
|
||||
function testExpandEvents2() {
|
||||
var n = new goog.ui.tree.BaseNode('');
|
||||
n.getTree = function() {};
|
||||
var expanded = true;
|
||||
n.setExpanded(expanded);
|
||||
assertEquals(expanded, n.getExpanded());
|
||||
var callCount = 0;
|
||||
n.addEventListener(goog.ui.tree.BaseNode.EventType.BEFORE_COLLAPSE,
|
||||
function(e) {
|
||||
assertEquals(expanded, n.getExpanded());
|
||||
callCount++;
|
||||
});
|
||||
n.addEventListener(goog.ui.tree.BaseNode.EventType.COLLAPSE,
|
||||
function(e) {
|
||||
assertEquals(!expanded, n.getExpanded());
|
||||
callCount++;
|
||||
});
|
||||
n.setExpanded(!expanded);
|
||||
assertEquals(2, callCount);
|
||||
}
|
||||
|
||||
function testExpandEventsPreventDefault() {
|
||||
var n = new goog.ui.tree.BaseNode('');
|
||||
n.getTree = function() {};
|
||||
var expanded = true;
|
||||
n.setExpanded(expanded);
|
||||
assertEquals(expanded, n.getExpanded());
|
||||
var callCount = 0;
|
||||
n.addEventListener(goog.ui.tree.BaseNode.EventType.BEFORE_COLLAPSE,
|
||||
function(e) {
|
||||
assertEquals(expanded, n.getExpanded());
|
||||
e.preventDefault();
|
||||
callCount++;
|
||||
});
|
||||
n.addEventListener(goog.ui.tree.BaseNode.EventType.COLLAPSE,
|
||||
function(e) {
|
||||
fail('Should not fire COLLAPSE');
|
||||
});
|
||||
n.setExpanded(!expanded);
|
||||
assertEquals(1, callCount);
|
||||
}
|
||||
|
||||
function testExpandEventsPreventDefault2() {
|
||||
var n = new goog.ui.tree.BaseNode('');
|
||||
n.getTree = function() {};
|
||||
var expanded = false;
|
||||
n.setExpanded(expanded);
|
||||
assertEquals(expanded, n.getExpanded());
|
||||
var callCount = 0;
|
||||
n.addEventListener(goog.ui.tree.BaseNode.EventType.BEFORE_EXPAND,
|
||||
function(e) {
|
||||
assertEquals(expanded, n.getExpanded());
|
||||
e.preventDefault();
|
||||
callCount++;
|
||||
});
|
||||
n.addEventListener(goog.ui.tree.BaseNode.EventType.EXPAND,
|
||||
function(e) {
|
||||
fail('Should not fire EXPAND');
|
||||
});
|
||||
n.setExpanded(!expanded);
|
||||
assertEquals(1, callCount);
|
||||
}
|
||||
|
||||
function testGetNextShownNode() {
|
||||
var tree = new goog.ui.tree.TreeControl('tree');
|
||||
assertNull('next node for unpopulated tree', tree.getNextShownNode());
|
||||
|
||||
var node1 = new goog.ui.tree.TreeNode('node1');
|
||||
var node2 = new goog.ui.tree.TreeNode('node2');
|
||||
var node3 = new goog.ui.tree.TreeNode('node3');
|
||||
node1.add(node2);
|
||||
node1.add(node3);
|
||||
|
||||
assertNull('next node for unexpanded node1', node1.getNextShownNode());
|
||||
node1.expand();
|
||||
assertEquals('next node for expanded node1', node2, node1.getNextShownNode());
|
||||
assertEquals('next node for node2', node3, node2.getNextShownNode());
|
||||
assertNull('next node for node3', node3.getNextShownNode());
|
||||
|
||||
tree.add(node1);
|
||||
assertEquals('next node for populated tree', node1, tree.getNextShownNode());
|
||||
assertNull('next node for node3 inside the tree', node3.getNextShownNode());
|
||||
|
||||
var component = new goog.ui.Component();
|
||||
component.addChild(tree);
|
||||
assertNull('next node for node3 inside the tree if the tree has parent',
|
||||
node3.getNextShownNode());
|
||||
}
|
||||
|
||||
function testGetPreviousShownNode() {
|
||||
var tree = new goog.ui.tree.TreeControl('tree');
|
||||
assertNull('next node for unpopulated tree', tree.getPreviousShownNode());
|
||||
|
||||
var node1 = new goog.ui.tree.TreeNode('node1');
|
||||
var node2 = new goog.ui.tree.TreeNode('node2');
|
||||
var node3 = new goog.ui.tree.TreeNode('node3');
|
||||
tree.add(node1);
|
||||
node1.add(node2);
|
||||
tree.add(node3);
|
||||
|
||||
assertEquals('prev node for node3 when node1 is unexpanded',
|
||||
node1, node3.getPreviousShownNode());
|
||||
node1.expand();
|
||||
assertEquals('prev node for node3 when node1 is expanded',
|
||||
node2, node3.getPreviousShownNode());
|
||||
assertEquals('prev node for node2 when node1 is expanded',
|
||||
node1, node2.getPreviousShownNode());
|
||||
assertEquals('prev node for node1 when root is shown', tree,
|
||||
node1.getPreviousShownNode());
|
||||
tree.setShowRootNode(false);
|
||||
assertNull('next node for node1 when root is not shown',
|
||||
node1.getPreviousShownNode());
|
||||
|
||||
var component = new goog.ui.Component();
|
||||
component.addChild(tree);
|
||||
assertNull('prev node for root if the tree has parent',
|
||||
tree.getPreviousShownNode());
|
||||
}
|
||||
|
||||
function testInvisibleNodesInUnrenderedTree() {
|
||||
var tree = new goog.ui.tree.TreeControl('tree');
|
||||
var a = new goog.ui.tree.TreeNode('a');
|
||||
var b = new goog.ui.tree.TreeNode('b');
|
||||
tree.add(a);
|
||||
a.add(b);
|
||||
tree.render();
|
||||
|
||||
var textContent =
|
||||
tree.getElement().textContent || tree.getElement().innerText;
|
||||
assertContains('Node should be rendered.', 'tree', textContent);
|
||||
assertContains('Node should be rendered.', 'a', textContent);
|
||||
assertNotContains('Unexpanded node child should not be rendered.',
|
||||
'b', textContent);
|
||||
|
||||
a.expand();
|
||||
var textContent =
|
||||
tree.getElement().textContent || tree.getElement().innerText;
|
||||
assertContains('Node should be rendered.', 'tree', textContent);
|
||||
assertContains('Node should be rendered.', 'a', textContent);
|
||||
assertContains('Expanded node child should be rendered.', 'b', textContent);
|
||||
tree.dispose();
|
||||
}
|
||||
|
||||
function testInvisibleNodesInRenderedTree() {
|
||||
var tree = new goog.ui.tree.TreeControl('tree');
|
||||
tree.render();
|
||||
var a = new goog.ui.tree.TreeNode('a');
|
||||
var b = new goog.ui.tree.TreeNode('b');
|
||||
tree.add(a);
|
||||
a.add(b);
|
||||
|
||||
var textContent =
|
||||
tree.getElement().textContent || tree.getElement().innerText;
|
||||
assertContains('Node should be rendered.', 'tree', textContent);
|
||||
assertContains('Node should be rendered.', 'a', textContent);
|
||||
assertNotContains('Unexpanded node child should not be rendered.',
|
||||
'b', textContent);
|
||||
|
||||
a.expand();
|
||||
var textContent =
|
||||
tree.getElement().textContent || tree.getElement().innerText;
|
||||
assertContains('Node should be rendered.', 'tree', textContent);
|
||||
assertContains('Node should be rendered.', 'a', textContent);
|
||||
assertContains('Expanded node child should be rendered.', 'b', textContent);
|
||||
tree.dispose();
|
||||
}
|
||||
|
||||
function testConstructor() {
|
||||
var tree = new goog.ui.tree.TreeControl('tree');
|
||||
assertEquals('tree', tree.getHtml());
|
||||
|
||||
tree = new goog.ui.tree.TreeControl(
|
||||
goog.html.testing.newSafeHtmlForTest('tree'));
|
||||
assertEquals('tree', tree.getHtml());
|
||||
}
|
||||
|
||||
function testSetHtml() {
|
||||
var tree = new goog.ui.tree.TreeControl('');
|
||||
tree.setHtml('<b>tree</b>');
|
||||
assertEquals('<b>tree</b>', tree.getHtml());
|
||||
|
||||
tree = new goog.ui.tree.TreeControl('');
|
||||
tree.setSafeHtml(goog.html.testing.newSafeHtmlForTest('<b>tree</b>'));
|
||||
assertEquals('<b>tree</b>', tree.getHtml());
|
||||
}
|
||||
|
||||
function testSetHtml_guardedByGlobalFlag() {
|
||||
stubs.set(goog.html.legacyconversions, 'ALLOW_LEGACY_CONVERSIONS', false);
|
||||
assertEquals(
|
||||
'Error: Legacy conversion from string to goog.html types is disabled',
|
||||
assertThrows(function() {
|
||||
new goog.ui.tree.TreeControl('<img src="blag" onerror="evil();">');
|
||||
}).message);
|
||||
}
|
||||
@@ -0,0 +1,642 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the goog.ui.tree.TreeControl class, which
|
||||
* provides a way to view a hierarchical set of data.
|
||||
*
|
||||
* @author arv@google.com (Erik Arvidsson)
|
||||
* @author eae@google.com (Emil A Eklund)
|
||||
*
|
||||
* This is a based on the webfx tree control. It since been updated to add
|
||||
* typeahead support, as well as accessibility support using ARIA framework.
|
||||
*
|
||||
* @see ../../demos/tree/demo.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.tree.TreeControl');
|
||||
|
||||
goog.require('goog.a11y.aria');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.events.FocusHandler');
|
||||
goog.require('goog.events.KeyHandler');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.ui.tree.BaseNode');
|
||||
goog.require('goog.ui.tree.TreeNode');
|
||||
goog.require('goog.ui.tree.TypeAhead');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This creates a TreeControl object. A tree control provides a way to
|
||||
* view a hierarchical set of data.
|
||||
* @param {string|!goog.html.SafeHtml} html The HTML content of the node label.
|
||||
* @param {Object=} opt_config The configuration for the tree. See
|
||||
* goog.ui.tree.TreeControl.defaultConfig. If not specified, a default config
|
||||
* will be used.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
|
||||
* @constructor
|
||||
* @extends {goog.ui.tree.BaseNode}
|
||||
*/
|
||||
goog.ui.tree.TreeControl = function(html, opt_config, opt_domHelper) {
|
||||
goog.ui.tree.BaseNode.call(this, html, opt_config, opt_domHelper);
|
||||
|
||||
// The root is open and selected by default.
|
||||
this.setExpandedInternal(true);
|
||||
this.setSelectedInternal(true);
|
||||
|
||||
this.selectedItem_ = this;
|
||||
|
||||
/**
|
||||
* Used for typeahead support.
|
||||
* @type {!goog.ui.tree.TypeAhead}
|
||||
* @private
|
||||
*/
|
||||
this.typeAhead_ = new goog.ui.tree.TypeAhead();
|
||||
|
||||
if (goog.userAgent.IE) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
// works since IE6SP1
|
||||
document.execCommand('BackgroundImageCache', false, true);
|
||||
} catch (e) {
|
||||
goog.log.warning(this.logger_, 'Failed to enable background image cache');
|
||||
}
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.ui.tree.TreeControl, goog.ui.tree.BaseNode);
|
||||
|
||||
|
||||
/**
|
||||
* The object handling keyboard events.
|
||||
* @type {goog.events.KeyHandler}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.keyHandler_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The object handling focus events.
|
||||
* @type {goog.events.FocusHandler}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.focusHandler_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Logger
|
||||
* @type {goog.log.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.logger_ =
|
||||
goog.log.getLogger('goog.ui.tree.TreeControl');
|
||||
|
||||
|
||||
/**
|
||||
* Whether the tree is focused.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.focused_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Child node that currently has focus.
|
||||
* @type {goog.ui.tree.BaseNode}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.focusedNode_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show lines.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.showLines_ = true;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show expanded lines.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.showExpandIcons_ = true;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show the root node.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.showRootNode_ = true;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show the root lines.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.showRootLines_ = true;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.tree.TreeControl.prototype.getTree = function() {
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.tree.TreeControl.prototype.getDepth = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Expands the parent chain of this node so that it is visible.
|
||||
* @override
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.reveal = function() {
|
||||
// always expanded by default
|
||||
// needs to be overriden so that we don't try to reveal our parent
|
||||
// which is a generic component
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles focus on the tree.
|
||||
* @param {!goog.events.BrowserEvent} e The browser event.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.handleFocus_ = function(e) {
|
||||
this.focused_ = true;
|
||||
goog.dom.classlist.add(
|
||||
goog.asserts.assert(this.getElement()),
|
||||
goog.getCssName('focused'));
|
||||
|
||||
if (this.selectedItem_) {
|
||||
this.selectedItem_.select();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles blur on the tree.
|
||||
* @param {!goog.events.BrowserEvent} e The browser event.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.handleBlur_ = function(e) {
|
||||
this.focused_ = false;
|
||||
goog.dom.classlist.remove(
|
||||
goog.asserts.assert(this.getElement()),
|
||||
goog.getCssName('focused'));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the tree has keyboard focus.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.hasFocus = function() {
|
||||
return this.focused_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.tree.TreeControl.prototype.getExpanded = function() {
|
||||
return !this.showRootNode_ ||
|
||||
goog.ui.tree.TreeControl.superClass_.getExpanded.call(this);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.tree.TreeControl.prototype.setExpanded = function(expanded) {
|
||||
if (!this.showRootNode_) {
|
||||
this.setExpandedInternal(expanded);
|
||||
} else {
|
||||
goog.ui.tree.TreeControl.superClass_.setExpanded.call(this, expanded);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.tree.TreeControl.prototype.getExpandIconSafeHtml = function() {
|
||||
// no expand icon for root element
|
||||
return goog.html.SafeHtml.EMPTY;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.tree.TreeControl.prototype.getIconElement = function() {
|
||||
var el = this.getRowElement();
|
||||
return el ? /** @type {Element} */ (el.firstChild) : null;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.tree.TreeControl.prototype.getExpandIconElement = function() {
|
||||
// no expand icon for root element
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.tree.TreeControl.prototype.updateExpandIcon = function() {
|
||||
// no expand icon
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.tree.TreeControl.prototype.getRowClassName = function() {
|
||||
return goog.ui.tree.TreeControl.superClass_.getRowClassName.call(this) +
|
||||
(this.showRootNode_ ? '' : ' ' + this.getConfig().cssHideRoot);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the source for the icon.
|
||||
* @return {string} Src for the icon.
|
||||
* @override
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.getCalculatedIconClass = function() {
|
||||
var expanded = this.getExpanded();
|
||||
var expandedIconClass = this.getExpandedIconClass();
|
||||
if (expanded && expandedIconClass) {
|
||||
return expandedIconClass;
|
||||
}
|
||||
var iconClass = this.getIconClass();
|
||||
if (!expanded && iconClass) {
|
||||
return iconClass;
|
||||
}
|
||||
|
||||
// fall back on default icons
|
||||
var config = this.getConfig();
|
||||
if (expanded && config.cssExpandedRootIcon) {
|
||||
return config.cssTreeIcon + ' ' + config.cssExpandedRootIcon;
|
||||
} else if (!expanded && config.cssCollapsedRootIcon) {
|
||||
return config.cssTreeIcon + ' ' + config.cssCollapsedRootIcon;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the selected item.
|
||||
* @param {goog.ui.tree.BaseNode} node The item to select.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.setSelectedItem = function(node) {
|
||||
if (this.selectedItem_ == node) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hadFocus = false;
|
||||
if (this.selectedItem_) {
|
||||
hadFocus = this.selectedItem_ == this.focusedNode_;
|
||||
this.selectedItem_.setSelectedInternal(false);
|
||||
}
|
||||
|
||||
this.selectedItem_ = node;
|
||||
|
||||
if (node) {
|
||||
node.setSelectedInternal(true);
|
||||
if (hadFocus) {
|
||||
node.select();
|
||||
}
|
||||
}
|
||||
|
||||
this.dispatchEvent(goog.events.EventType.CHANGE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the selected item.
|
||||
* @return {goog.ui.tree.BaseNode} The currently selected item.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.getSelectedItem = function() {
|
||||
return this.selectedItem_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether to show lines.
|
||||
* @param {boolean} b Whether to show lines.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.setShowLines = function(b) {
|
||||
if (this.showLines_ != b) {
|
||||
this.showLines_ = b;
|
||||
if (this.isInDocument()) {
|
||||
this.updateLinesAndExpandIcons_();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether to show lines.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.getShowLines = function() {
|
||||
return this.showLines_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Updates the lines after the tree has been drawn.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.updateLinesAndExpandIcons_ = function() {
|
||||
var tree = this;
|
||||
var showLines = tree.getShowLines();
|
||||
var showRootLines = tree.getShowRootLines();
|
||||
|
||||
/**
|
||||
* Recursively walk through all nodes and update the class names of the
|
||||
* expand icon and the children element.
|
||||
* @param {!goog.ui.tree.BaseNode} node
|
||||
*/
|
||||
function updateShowLines(node) {
|
||||
var childrenEl = node.getChildrenElement();
|
||||
if (childrenEl) {
|
||||
var hideLines = !showLines || tree == node.getParent() && !showRootLines;
|
||||
var childClass = hideLines ? node.getConfig().cssChildrenNoLines :
|
||||
node.getConfig().cssChildren;
|
||||
childrenEl.className = childClass;
|
||||
|
||||
var expandIconEl = node.getExpandIconElement();
|
||||
if (expandIconEl) {
|
||||
expandIconEl.className = node.getExpandIconClass();
|
||||
}
|
||||
}
|
||||
node.forEachChild(updateShowLines);
|
||||
}
|
||||
updateShowLines(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether to show root lines.
|
||||
* @param {boolean} b Whether to show root lines.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.setShowRootLines = function(b) {
|
||||
if (this.showRootLines_ != b) {
|
||||
this.showRootLines_ = b;
|
||||
if (this.isInDocument()) {
|
||||
this.updateLinesAndExpandIcons_();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether to show root lines.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.getShowRootLines = function() {
|
||||
return this.showRootLines_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether to show expand icons.
|
||||
* @param {boolean} b Whether to show expand icons.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.setShowExpandIcons = function(b) {
|
||||
if (this.showExpandIcons_ != b) {
|
||||
this.showExpandIcons_ = b;
|
||||
if (this.isInDocument()) {
|
||||
this.updateLinesAndExpandIcons_();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether to show expand icons.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.getShowExpandIcons = function() {
|
||||
return this.showExpandIcons_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether to show the root node.
|
||||
* @param {boolean} b Whether to show the root node.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.setShowRootNode = function(b) {
|
||||
if (this.showRootNode_ != b) {
|
||||
this.showRootNode_ = b;
|
||||
if (this.isInDocument()) {
|
||||
var el = this.getRowElement();
|
||||
if (el) {
|
||||
el.className = this.getRowClassName();
|
||||
}
|
||||
}
|
||||
// Ensure that we do not hide the selected item.
|
||||
if (!b && this.getSelectedItem() == this && this.getFirstChild()) {
|
||||
this.setSelectedItem(this.getFirstChild());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether to show the root node.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.getShowRootNode = function() {
|
||||
return this.showRootNode_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add roles and states.
|
||||
* @protected
|
||||
* @override
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.initAccessibility = function() {
|
||||
goog.ui.tree.TreeControl.superClass_.initAccessibility.call(this);
|
||||
|
||||
var elt = this.getElement();
|
||||
goog.asserts.assert(elt, 'The DOM element for the tree cannot be null.');
|
||||
goog.a11y.aria.setRole(elt, 'tree');
|
||||
goog.a11y.aria.setState(elt, 'labelledby', this.getLabelElement().id);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.tree.TreeControl.prototype.enterDocument = function() {
|
||||
goog.ui.tree.TreeControl.superClass_.enterDocument.call(this);
|
||||
var el = this.getElement();
|
||||
el.className = this.getConfig().cssRoot;
|
||||
el.setAttribute('hideFocus', 'true');
|
||||
this.attachEvents_();
|
||||
this.initAccessibility();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.tree.TreeControl.prototype.exitDocument = function() {
|
||||
goog.ui.tree.TreeControl.superClass_.exitDocument.call(this);
|
||||
this.detachEvents_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds the event listeners to the tree.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.attachEvents_ = function() {
|
||||
var el = this.getElement();
|
||||
el.tabIndex = 0;
|
||||
|
||||
var kh = this.keyHandler_ = new goog.events.KeyHandler(el);
|
||||
var fh = this.focusHandler_ = new goog.events.FocusHandler(el);
|
||||
|
||||
this.getHandler().
|
||||
listen(fh, goog.events.FocusHandler.EventType.FOCUSOUT, this.handleBlur_).
|
||||
listen(fh, goog.events.FocusHandler.EventType.FOCUSIN, this.handleFocus_).
|
||||
listen(kh, goog.events.KeyHandler.EventType.KEY, this.handleKeyEvent).
|
||||
listen(el, goog.events.EventType.MOUSEDOWN, this.handleMouseEvent_).
|
||||
listen(el, goog.events.EventType.CLICK, this.handleMouseEvent_).
|
||||
listen(el, goog.events.EventType.DBLCLICK, this.handleMouseEvent_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the event listeners from the tree.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.detachEvents_ = function() {
|
||||
this.keyHandler_.dispose();
|
||||
this.keyHandler_ = null;
|
||||
this.focusHandler_.dispose();
|
||||
this.focusHandler_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles mouse events.
|
||||
* @param {!goog.events.BrowserEvent} e The browser event.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.handleMouseEvent_ = function(e) {
|
||||
goog.log.fine(this.logger_, 'Received event ' + e.type);
|
||||
var node = this.getNodeFromEvent_(e);
|
||||
if (node) {
|
||||
switch (e.type) {
|
||||
case goog.events.EventType.MOUSEDOWN:
|
||||
node.onMouseDown(e);
|
||||
break;
|
||||
case goog.events.EventType.CLICK:
|
||||
node.onClick_(e);
|
||||
break;
|
||||
case goog.events.EventType.DBLCLICK:
|
||||
node.onDoubleClick_(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles key down on the tree.
|
||||
* @param {!goog.events.BrowserEvent} e The browser event.
|
||||
* @return {boolean} The handled value.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.handleKeyEvent = function(e) {
|
||||
var handled = false;
|
||||
|
||||
// Handle typeahead and navigation keystrokes.
|
||||
handled = this.typeAhead_.handleNavigation(e) ||
|
||||
(this.selectedItem_ && this.selectedItem_.onKeyDown(e)) ||
|
||||
this.typeAhead_.handleTypeAheadChar(e);
|
||||
|
||||
if (handled) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
return handled;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds the containing node given an event.
|
||||
* @param {!goog.events.BrowserEvent} e The browser event.
|
||||
* @return {goog.ui.tree.BaseNode} The containing node or null if no node is
|
||||
* found.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.getNodeFromEvent_ = function(e) {
|
||||
// find the right node
|
||||
var node = null;
|
||||
var target = e.target;
|
||||
while (target != null) {
|
||||
var id = target.id;
|
||||
node = goog.ui.tree.BaseNode.allNodes[id];
|
||||
if (node) {
|
||||
return node;
|
||||
}
|
||||
if (target == this.getElement()) {
|
||||
break;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new tree node using the same config as the root.
|
||||
* @param {string=} opt_html The HTML content of the node label.
|
||||
* @return {!goog.ui.tree.TreeNode} The new item.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.createNode = function(opt_html) {
|
||||
return new goog.ui.tree.TreeNode(opt_html || goog.html.SafeHtml.EMPTY,
|
||||
this.getConfig(), this.getDomHelper());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allows the caller to notify that the given node has been added or just had
|
||||
* been updated in the tree.
|
||||
* @param {goog.ui.tree.BaseNode} node New node being added or existing node
|
||||
* that just had been updated.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.setNode = function(node) {
|
||||
this.typeAhead_.setNodeInMap(node);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allows the caller to notify that the given node is being removed from the
|
||||
* tree.
|
||||
* @param {goog.ui.tree.BaseNode} node Node being removed.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.removeNode = function(node) {
|
||||
this.typeAhead_.removeNodeFromMap(node);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clear the typeahead buffer.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.prototype.clearTypeAhead = function() {
|
||||
this.typeAhead_.clear();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A default configuration for the tree.
|
||||
*/
|
||||
goog.ui.tree.TreeControl.defaultConfig = goog.ui.tree.BaseNode.defaultConfig;
|
||||
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.ui.tree.TreeControl
|
||||
</title>
|
||||
<script type="text/javascript" src="../../base.js">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
goog.require('goog.ui.tree.TreeControlTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="treeContainer" style="width:400px">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.ui.tree.TreeControlTest');
|
||||
goog.setTestOnly('goog.ui.tree.TreeControlTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.ui.tree.TreeControl');
|
||||
function makeATree() {
|
||||
var tree = new goog.ui.tree.TreeControl('root');
|
||||
var testData = ['A', [
|
||||
['AA', [
|
||||
['AAA', []],
|
||||
['AAB', []]
|
||||
]],
|
||||
['AB', [
|
||||
['ABA', []],
|
||||
['ABB', []]
|
||||
]]
|
||||
]];
|
||||
|
||||
createTreeFromTestData(tree, testData, 3);
|
||||
tree.render(goog.dom.getElement('treeContainer'));
|
||||
return tree;
|
||||
}
|
||||
|
||||
function createTreeFromTestData(node, data, maxLevels) {
|
||||
node.setHtml(data[0]);
|
||||
if (maxLevels < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var children = data[1];
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
var childNode = node.getTree().createNode('');
|
||||
node.add(childNode);
|
||||
createTreeFromTestData(childNode, child, maxLevels - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test moving a node to a greater depth.
|
||||
*/
|
||||
function testIndent() {
|
||||
var tree = makeATree();
|
||||
tree.expandAll();
|
||||
|
||||
var node = tree.getChildren()[0].getChildren()[0];
|
||||
assertEquals('AAA', node.getHtml());
|
||||
assertNotNull(node.getElement());
|
||||
assertEquals('19px', node.getRowElement().style.paddingLeft);
|
||||
|
||||
assertEquals(2, node.getDepth());
|
||||
|
||||
var newParent = node.getNextSibling();
|
||||
assertEquals('AAB', newParent.getHtml());
|
||||
assertEquals(2, newParent.getDepth());
|
||||
|
||||
newParent.add(node);
|
||||
|
||||
assertEquals(newParent, node.getParent());
|
||||
assertEquals(node, newParent.getChildren()[0]);
|
||||
assertEquals(3, node.getDepth());
|
||||
assertEquals('38px', node.getRowElement().style.paddingLeft);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test moving a node to a lesser depth.
|
||||
*/
|
||||
function testOutdent() {
|
||||
var tree = makeATree();
|
||||
tree.expandAll();
|
||||
|
||||
var node = tree.getChildren()[0].getChildren()[0];
|
||||
assertEquals('AAA', node.getHtml());
|
||||
assertNotNull(node.getElement());
|
||||
assertEquals('19px', node.getRowElement().style.paddingLeft);
|
||||
|
||||
assertEquals(2, node.getDepth());
|
||||
|
||||
var newParent = tree;
|
||||
assertEquals('A', newParent.getHtml());
|
||||
assertEquals(0, newParent.getDepth());
|
||||
|
||||
newParent.add(node);
|
||||
|
||||
assertEquals(newParent, node.getParent());
|
||||
assertEquals(node, newParent.getChildren()[2]);
|
||||
assertEquals(1, node.getDepth());
|
||||
assertEquals('0px', node.getRowElement().style.paddingLeft);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the goog.ui.tree.TreeNode class.
|
||||
*
|
||||
* @author arv@google.com (Erik Arvidsson)
|
||||
* @author eae@google.com (Emil A Eklund)
|
||||
*
|
||||
* This is a based on the webfx tree control. See file comment in
|
||||
* treecontrol.js.
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.tree.TreeNode');
|
||||
|
||||
goog.require('goog.ui.tree.BaseNode');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A single node in the tree.
|
||||
* @param {string|!goog.html.SafeHtml} html The html content of the node label.
|
||||
* @param {Object=} opt_config The configuration for the tree. See
|
||||
* goog.ui.tree.TreeControl.defaultConfig. If not specified, a default config
|
||||
* will be used.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
|
||||
* @constructor
|
||||
* @extends {goog.ui.tree.BaseNode}
|
||||
*/
|
||||
goog.ui.tree.TreeNode = function(html, opt_config, opt_domHelper) {
|
||||
goog.ui.tree.BaseNode.call(this, html, opt_config, opt_domHelper);
|
||||
};
|
||||
goog.inherits(goog.ui.tree.TreeNode, goog.ui.tree.BaseNode);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the tree.
|
||||
* @return {?goog.ui.tree.TreeControl} The tree.
|
||||
* @override
|
||||
*/
|
||||
goog.ui.tree.TreeNode.prototype.getTree = function() {
|
||||
if (this.tree) {
|
||||
return this.tree;
|
||||
}
|
||||
var parent = this.getParent();
|
||||
if (parent) {
|
||||
var tree = parent.getTree();
|
||||
if (tree) {
|
||||
this.setTreeInternal(tree);
|
||||
return tree;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the source for the icon.
|
||||
* @return {string} Src for the icon.
|
||||
* @override
|
||||
*/
|
||||
goog.ui.tree.TreeNode.prototype.getCalculatedIconClass = function() {
|
||||
var expanded = this.getExpanded();
|
||||
var expandedIconClass = this.getExpandedIconClass();
|
||||
if (expanded && expandedIconClass) {
|
||||
return expandedIconClass;
|
||||
}
|
||||
var iconClass = this.getIconClass();
|
||||
if (!expanded && iconClass) {
|
||||
return iconClass;
|
||||
}
|
||||
|
||||
// fall back on default icons
|
||||
var config = this.getConfig();
|
||||
if (this.hasChildren()) {
|
||||
if (expanded && config.cssExpandedFolderIcon) {
|
||||
return config.cssTreeIcon + ' ' +
|
||||
config.cssExpandedFolderIcon;
|
||||
} else if (!expanded && config.cssCollapsedFolderIcon) {
|
||||
return config.cssTreeIcon + ' ' +
|
||||
config.cssCollapsedFolderIcon;
|
||||
}
|
||||
} else {
|
||||
if (config.cssFileIcon) {
|
||||
return config.cssTreeIcon + ' ' + config.cssFileIcon;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
@@ -0,0 +1,332 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides the typeahead functionality for the tree class.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.tree.TypeAhead');
|
||||
goog.provide('goog.ui.tree.TypeAhead.Offset');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.events.KeyCodes');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.structs.Trie');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a TypeAhead object.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.ui.tree.TypeAhead = function() {
|
||||
this.nodeMap_ = new goog.structs.Trie();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Map of tree nodes to allow for quick access by characters in the label text.
|
||||
* @type {goog.structs.Trie<Array<goog.ui.tree.BaseNode>>}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.nodeMap_;
|
||||
|
||||
|
||||
/**
|
||||
* Buffer for storing typeahead characters.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.buffer_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* Matching labels from the latest typeahead search.
|
||||
* @type {?Array<string>}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.matchingLabels_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Matching nodes from the latest typeahead search. Used when more than
|
||||
* one node is present with the same label text.
|
||||
* @type {?Array<goog.ui.tree.BaseNode>}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.matchingNodes_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the current index of the label from the latest typeahead search.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.matchingLabelIndex_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the index into matching nodes when more than one node is found
|
||||
* with the same label.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.matchingNodeIndex_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Enum for offset values that are used for ctrl-key navigation among the
|
||||
* multiple matches of a given typeahead buffer.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.Offset = {
|
||||
DOWN: 1,
|
||||
UP: -1
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles navigation keys.
|
||||
* @param {goog.events.BrowserEvent} e The browser event.
|
||||
* @return {boolean} The handled value.
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.handleNavigation = function(e) {
|
||||
var handled = false;
|
||||
|
||||
switch (e.keyCode) {
|
||||
// Handle ctrl+down, ctrl+up to navigate within typeahead results.
|
||||
case goog.events.KeyCodes.DOWN:
|
||||
case goog.events.KeyCodes.UP:
|
||||
if (e.ctrlKey) {
|
||||
this.jumpTo_(e.keyCode == goog.events.KeyCodes.DOWN ?
|
||||
goog.ui.tree.TypeAhead.Offset.DOWN :
|
||||
goog.ui.tree.TypeAhead.Offset.UP);
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// Remove the last typeahead char.
|
||||
case goog.events.KeyCodes.BACKSPACE:
|
||||
var length = this.buffer_.length - 1;
|
||||
handled = true;
|
||||
if (length > 0) {
|
||||
this.buffer_ = this.buffer_.substring(0, length);
|
||||
this.jumpToLabel_(this.buffer_);
|
||||
} else if (length == 0) {
|
||||
// Clear the last character in typeahead.
|
||||
this.buffer_ = '';
|
||||
} else {
|
||||
handled = false;
|
||||
}
|
||||
break;
|
||||
|
||||
// Clear typeahead buffer.
|
||||
case goog.events.KeyCodes.ESC:
|
||||
this.buffer_ = '';
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return handled;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the character presses.
|
||||
* @param {goog.events.BrowserEvent} e The browser event.
|
||||
* Expected event type is goog.events.KeyHandler.EventType.KEY.
|
||||
* @return {boolean} The handled value.
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.handleTypeAheadChar = function(e) {
|
||||
var handled = false;
|
||||
|
||||
if (!e.ctrlKey && !e.altKey) {
|
||||
// Since goog.structs.Trie.getKeys compares characters during
|
||||
// lookup, we should use charCode instead of keyCode where possible.
|
||||
// Convert to lowercase, typeahead is case insensitive.
|
||||
var ch = String.fromCharCode(e.charCode || e.keyCode).toLowerCase();
|
||||
if (goog.string.isUnicodeChar(ch) && (ch != ' ' || this.buffer_)) {
|
||||
this.buffer_ += ch;
|
||||
handled = this.jumpToLabel_(this.buffer_);
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds or updates the given node in the nodemap. The label text is used as a
|
||||
* key and the node id is used as a value. In the case that the key already
|
||||
* exists, such as when more than one node exists with the same label, then this
|
||||
* function creates an array to hold the multiple nodes.
|
||||
* @param {goog.ui.tree.BaseNode} node Node to be added or updated.
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.setNodeInMap = function(node) {
|
||||
var labelText = node.getText();
|
||||
if (labelText && !goog.string.isEmptyOrWhitespace(goog.string.makeSafe(labelText))) {
|
||||
// Typeahead is case insensitive, convert to lowercase.
|
||||
labelText = labelText.toLowerCase();
|
||||
|
||||
var previousValue = this.nodeMap_.get(labelText);
|
||||
if (previousValue) {
|
||||
// Found a previously created array, add the given node.
|
||||
previousValue.push(node);
|
||||
} else {
|
||||
// Create a new array and set the array as value.
|
||||
var nodeList = [node];
|
||||
this.nodeMap_.set(labelText, nodeList);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the given node from the nodemap.
|
||||
* @param {goog.ui.tree.BaseNode} node Node to be removed.
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.removeNodeFromMap = function(node) {
|
||||
var labelText = node.getText();
|
||||
if (labelText && !goog.string.isEmptyOrWhitespace(goog.string.makeSafe(labelText))) {
|
||||
labelText = labelText.toLowerCase();
|
||||
|
||||
var nodeList = this.nodeMap_.get(labelText);
|
||||
if (nodeList) {
|
||||
// Remove the node from the array.
|
||||
goog.array.remove(nodeList, node);
|
||||
if (!!nodeList.length) {
|
||||
this.nodeMap_.remove(labelText);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Select the first matching node for the given typeahead.
|
||||
* @param {string} typeAhead Typeahead characters to match.
|
||||
* @return {boolean} True iff a node is found.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.jumpToLabel_ = function(typeAhead) {
|
||||
var handled = false;
|
||||
var labels = this.nodeMap_.getKeys(typeAhead);
|
||||
|
||||
// Make sure we have at least one matching label.
|
||||
if (labels && labels.length) {
|
||||
this.matchingNodeIndex_ = 0;
|
||||
this.matchingLabelIndex_ = 0;
|
||||
|
||||
var nodes = this.nodeMap_.get(labels[0]);
|
||||
if ((handled = this.selectMatchingNode_(nodes))) {
|
||||
this.matchingLabels_ = labels;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(user): beep when no node is found
|
||||
return handled;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Select the next or previous node based on the offset.
|
||||
* @param {goog.ui.tree.TypeAhead.Offset} offset DOWN or UP.
|
||||
* @return {boolean} Whether a node is found.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.jumpTo_ = function(offset) {
|
||||
var handled = false;
|
||||
var labels = this.matchingLabels_;
|
||||
|
||||
if (labels) {
|
||||
var nodes = null;
|
||||
var nodeIndexOutOfRange = false;
|
||||
|
||||
// Navigate within the nodes array.
|
||||
if (this.matchingNodes_) {
|
||||
var newNodeIndex = this.matchingNodeIndex_ + offset;
|
||||
if (newNodeIndex >= 0 && newNodeIndex < this.matchingNodes_.length) {
|
||||
this.matchingNodeIndex_ = newNodeIndex;
|
||||
nodes = this.matchingNodes_;
|
||||
} else {
|
||||
nodeIndexOutOfRange = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Navigate to the next or previous label.
|
||||
if (!nodes) {
|
||||
var newLabelIndex = this.matchingLabelIndex_ + offset;
|
||||
if (newLabelIndex >= 0 && newLabelIndex < labels.length) {
|
||||
this.matchingLabelIndex_ = newLabelIndex;
|
||||
}
|
||||
|
||||
if (labels.length > this.matchingLabelIndex_) {
|
||||
nodes = this.nodeMap_.get(labels[this.matchingLabelIndex_]);
|
||||
}
|
||||
|
||||
// Handle the case where we are moving beyond the available nodes,
|
||||
// while going UP select the last item of multiple nodes with same label
|
||||
// and while going DOWN select the first item of next set of nodes
|
||||
if (nodes && nodes.length && nodeIndexOutOfRange) {
|
||||
this.matchingNodeIndex_ = (offset == goog.ui.tree.TypeAhead.Offset.UP) ?
|
||||
nodes.length - 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ((handled = this.selectMatchingNode_(nodes))) {
|
||||
this.matchingLabels_ = labels;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(user): beep when no node is found
|
||||
return handled;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Given a nodes array reveals and selects the node while using node index.
|
||||
* @param {Array<goog.ui.tree.BaseNode>|undefined} nodes Nodes array to select
|
||||
* the node from.
|
||||
* @return {boolean} Whether a matching node was found.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.selectMatchingNode_ = function(nodes) {
|
||||
var node;
|
||||
|
||||
if (nodes) {
|
||||
// Find the matching node.
|
||||
if (this.matchingNodeIndex_ < nodes.length) {
|
||||
node = nodes[this.matchingNodeIndex_];
|
||||
this.matchingNodes_ = nodes;
|
||||
}
|
||||
|
||||
if (node) {
|
||||
node.reveal();
|
||||
node.select();
|
||||
}
|
||||
}
|
||||
|
||||
return !!node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the typeahead buffer.
|
||||
*/
|
||||
goog.ui.tree.TypeAhead.prototype.clear = function() {
|
||||
this.buffer_ = '';
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.ui.tree.TypeAhead
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.ui.tree.TypeAheadTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="treeContainer" style="width:400px">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.ui.tree.TypeAheadTest');
|
||||
goog.setTestOnly('goog.ui.tree.TypeAheadTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events.KeyCodes');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.ui.tree.TreeControl');
|
||||
goog.require('goog.ui.tree.TypeAhead');
|
||||
function makeATree() {
|
||||
var tree = new goog.ui.tree.TreeControl('root');
|
||||
var testData = ['level1',
|
||||
[['level2', [['eve', []], ['eve2', []]], []],
|
||||
['level22', [['eve', []], ['eve3', []]], []]],
|
||||
[]];
|
||||
|
||||
createTreeFromTestData(tree, testData, 3);
|
||||
|
||||
tree.createDom();
|
||||
goog.dom.getElement('treeContainer').appendChild(tree.getElement());
|
||||
tree.enterDocument();
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
function createTreeFromTestData(node, data, maxLevels) {
|
||||
node.setHtml(data[0]);
|
||||
if (maxLevels < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var children = data[1];
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
var childNode = node.getTree().createNode('');
|
||||
node.add(childNode);
|
||||
createTreeFromTestData(childNode, child, maxLevels - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test jumpToLabel_ functionality.
|
||||
*/
|
||||
function testJumpToLabel() {
|
||||
var tree = makeATree();
|
||||
var typeAhead = tree.typeAhead_;
|
||||
|
||||
// Test the case when only one matching entry exists.
|
||||
var handled = typeAhead.jumpToLabel_('level1');
|
||||
var selectedItem = tree.getSelectedItem();
|
||||
assertTrue(handled && selectedItem.getHtml() == 'level1');
|
||||
|
||||
// Test the case when more than one matching entry exists.
|
||||
handled = typeAhead.jumpToLabel_('eve');
|
||||
selectedItem = tree.getSelectedItem();
|
||||
assertTrue(handled && selectedItem.getHtml() == 'eve');
|
||||
|
||||
// Test the case when the matching entry is at a deeper level.
|
||||
handled = typeAhead.jumpToLabel_('eve3');
|
||||
selectedItem = tree.getSelectedItem();
|
||||
assertTrue(handled && selectedItem.getHtml() == 'eve3');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test jumpTo_ functionality.
|
||||
*/
|
||||
function testJumpTo() {
|
||||
var tree = makeATree();
|
||||
var typeAhead = tree.typeAhead_;
|
||||
|
||||
// Jump to the first matching 'eve', followed by Ctrl+DOWN to jump to
|
||||
// second matching 'eve'
|
||||
var handled = typeAhead.jumpToLabel_('eve') &&
|
||||
typeAhead.jumpTo_(goog.ui.tree.TypeAhead.Offset.DOWN);
|
||||
var selectedItem = tree.getSelectedItem();
|
||||
assertTrue(handled && selectedItem.getHtml() == 'eve');
|
||||
|
||||
// Simulate a DOWN key on the tree, now the selection should be on 'eve3'
|
||||
var e = new Object();
|
||||
e.keyCode = goog.events.KeyCodes.DOWN;
|
||||
e.preventDefault = function() {};
|
||||
handled = tree.handleKeyEvent(e);
|
||||
selectedItem = tree.getSelectedItem();
|
||||
assertTrue(handled && selectedItem.getHtml() == 'eve3');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test handleTypeAheadChar functionality.
|
||||
*/
|
||||
function testHandleTypeAheadChar() {
|
||||
var tree = makeATree();
|
||||
var typeAhead = tree.typeAhead_;
|
||||
var e = new Object();
|
||||
|
||||
// Period character('.'): keyCode = 190, charCode = 46
|
||||
// String.fromCharCode(190) = '3/4' <-- incorrect
|
||||
// String.fromCharCode(46) = '.' <-- correct
|
||||
e.keyCode = goog.events.KeyCodes.PERIOD;
|
||||
e.charCode = 46;
|
||||
e.preventDefault = function() {};
|
||||
typeAhead.handleTypeAheadChar(e);
|
||||
assertEquals('.', typeAhead.buffer_);
|
||||
|
||||
// charCode not supplied.
|
||||
// This is expected to work only for alpha-num characters.
|
||||
e.keyCode = goog.events.KeyCodes.A;
|
||||
e.charCode = undefined;
|
||||
typeAhead.buffer_ = '';
|
||||
typeAhead.handleTypeAheadChar(e);
|
||||
assertEquals('a', typeAhead.buffer_);
|
||||
}
|
||||
Reference in New Issue
Block a user