Files
blockly/core/components/tree/treecontrol.js
Sam El-Husseini 3f4faeab46 Cleanup types under components/ (#3910)
* Fix types under components

* Fix menu
2020-05-20 16:54:53 -07:00

333 lines
8.1 KiB
JavaScript

/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Definition of the Blockly.tree.TreeControl class.
* This class is similar to Closure's goog.ui.tree.TreeControl class.
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.tree.TreeControl');
goog.require('Blockly.tree.TreeNode');
goog.require('Blockly.tree.BaseNode');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.style');
/**
* An extension of the TreeControl object in closure that provides
* a way to view a hierarchical set of data.
* Similar to Closure's goog.ui.tree.TreeControl
*
* @param {Blockly.Toolbox} toolbox The parent toolbox for this tree.
* @param {!Blockly.tree.BaseNode.Config} config The configuration for the tree.
* @constructor
* @extends {Blockly.tree.BaseNode}
*/
Blockly.tree.TreeControl = function(toolbox, config) {
this.toolbox_ = toolbox;
/**
* Click event data.
* @type {?Blockly.EventData}
* @private
*/
this.onClickWrapper_ = null;
/**
* Key down event data.
* @type {?Blockly.EventData}
* @private
*/
this.onKeydownWrapper_ = null;
Blockly.tree.BaseNode.call(this, '', config);
// The root is open and selected by default.
this.expanded_ = true;
this.selected_ = true;
/**
* Currently selected item.
* @type {Blockly.tree.BaseNode}
* @private
*/
this.selectedItem_ = this;
/**
* A handler that's triggered before a node is selected.
* @type {?function(Blockly.tree.BaseNode):boolean}
* @private
*/
this.onBeforeSelected_ = null;
/**
* A handler that's triggered before a node is selected.
* @type {?function(Blockly.tree.BaseNode, Blockly.tree.BaseNode):?}
* @private
*/
this.onAfterSelected_ = null;
};
Blockly.utils.object.inherits(Blockly.tree.TreeControl, Blockly.tree.BaseNode);
/**
* Returns the tree.
* @override
*/
Blockly.tree.TreeControl.prototype.getTree = function() {
return this;
};
/**
* Returns the associated toolbox.
* @return {Blockly.Toolbox} The toolbox.
* @package
*/
Blockly.tree.TreeControl.prototype.getToolbox = function() {
return this.toolbox_;
};
/**
* Return node depth.
* @override
*/
Blockly.tree.TreeControl.prototype.getDepth = function() {
return 0;
};
/** @override */
Blockly.tree.TreeControl.prototype.setExpanded = function(expanded) {
this.expanded_ = expanded;
};
/** @override */
Blockly.tree.TreeControl.prototype.getIconElement = function() {
var el = this.getRowElement();
return el ? /** @type {Element} */ (el.firstChild) : null;
};
/** @override */
Blockly.tree.TreeControl.prototype.updateExpandIcon = function() {
// no expand icon
};
/** @override */
Blockly.tree.TreeControl.prototype.getRowClassName = function() {
return Blockly.tree.TreeControl.superClass_.getRowClassName.call(this) +
' ' + this.config_.cssHideRoot;
};
/**
* Returns the source for the icon.
* @return {string} Src for the icon.
* @override
*/
Blockly.tree.TreeControl.prototype.getCalculatedIconClass = function() {
var expanded = this.expanded_;
if (expanded && this.expandedIconClass) {
return this.expandedIconClass;
}
var iconClass = this.iconClass;
if (!expanded && iconClass) {
return iconClass;
}
return '';
};
/**
* Sets the selected item.
* @param {Blockly.tree.BaseNode} node The item to select.
* @package
*/
Blockly.tree.TreeControl.prototype.setSelectedItem = function(node) {
if (node == this.selectedItem_) {
return;
}
if (this.onBeforeSelected_ &&
!this.onBeforeSelected_.call(this.toolbox_, node)) {
return;
}
var oldNode = this.getSelectedItem();
if (this.selectedItem_) {
this.selectedItem_.setSelected(false);
}
this.selectedItem_ = node;
if (node) {
node.setSelected(true);
}
if (this.onAfterSelected_) {
this.onAfterSelected_.call(this.toolbox_, oldNode, node);
}
};
/**
* Set the handler that's triggered before a node is selected.
* @param {function(Blockly.tree.BaseNode):boolean} fn The handler
* @package
*/
Blockly.tree.TreeControl.prototype.onBeforeSelected = function(fn) {
this.onBeforeSelected_ = fn;
};
/**
* Set the handler that's triggered after a node is selected.
* @param {function(
* Blockly.tree.BaseNode, Blockly.tree.BaseNode):?} fn The handler
* @package
*/
Blockly.tree.TreeControl.prototype.onAfterSelected = function(fn) {
this.onAfterSelected_ = fn;
};
/**
* Returns the selected item.
* @return {Blockly.tree.BaseNode} The currently selected item.
* @package
*/
Blockly.tree.TreeControl.prototype.getSelectedItem = function() {
return this.selectedItem_;
};
/**
* Add roles and states.
* @protected
* @override
*/
Blockly.tree.TreeControl.prototype.initAccessibility = function() {
Blockly.tree.TreeControl.superClass_.initAccessibility.call(this);
var el = /** @type {!Element} */ (this.getElement());
Blockly.utils.aria.setRole(el, Blockly.utils.aria.Role.TREE);
Blockly.utils.aria.setState(el,
Blockly.utils.aria.State.LABELLEDBY, this.getLabelElement().id);
};
/** @override */
Blockly.tree.TreeControl.prototype.enterDocument = function() {
Blockly.tree.TreeControl.superClass_.enterDocument.call(this);
var el = this.getElement();
el.className = this.config_.cssRoot;
el.setAttribute('hideFocus', 'true');
this.attachEvents_();
this.initAccessibility();
};
/** @override */
Blockly.tree.TreeControl.prototype.exitDocument = function() {
Blockly.tree.TreeControl.superClass_.exitDocument.call(this);
this.detachEvents_();
};
/**
* Adds the event listeners to the tree.
* @private
*/
Blockly.tree.TreeControl.prototype.attachEvents_ = function() {
var el = this.getElement();
el.tabIndex = 0;
this.onClickWrapper_ = Blockly.bindEventWithChecks_(el,
'click', this, this.handleMouseEvent_);
this.onKeydownWrapper_ = Blockly.bindEvent_(el,
'keydown', this, this.handleKeyEvent_);
};
/**
* Removes the event listeners from the tree.
* @private
*/
Blockly.tree.TreeControl.prototype.detachEvents_ = function() {
if (this.onClickWrapper_) {
Blockly.unbindEvent_(this.onClickWrapper_);
this.onClickWrapper_ = null;
}
if (this.onKeydownWrapper_) {
Blockly.unbindEvent_(this.onKeydownWrapper_);
this.onKeydownWrapper_ = null;
}
};
/**
* Handles mouse events.
* @param {!Event} e The browser event.
* @private
*/
Blockly.tree.TreeControl.prototype.handleMouseEvent_ = function(e) {
var node = this.getNodeFromEvent_(e);
if (node && e.type == 'click') {
node.onClick_(e);
}
};
/**
* Handles key down on the tree.
* @param {!Event} e The browser event.
* @return {boolean} The handled value.
* @private
*/
Blockly.tree.TreeControl.prototype.handleKeyEvent_ = function(e) {
// Handle navigation keystrokes.
var handled = !!(this.selectedItem_ && this.selectedItem_.onKeyDown(e));
if (handled) {
Blockly.utils.style.scrollIntoContainerView(
/** @type {!Element} */ (this.selectedItem_.getElement()),
/** @type {!Element} */ (this.getElement().parentNode));
e.preventDefault();
}
return handled;
};
/**
* Finds the containing node given an event.
* @param {!Event} e The browser event.
* @return {Blockly.tree.BaseNode} The containing node or null if no node is
* found.
* @private
*/
Blockly.tree.TreeControl.prototype.getNodeFromEvent_ = function(e) {
// find the right node
var node = null;
var target = e.target;
while (target) {
var id = target.id;
node = Blockly.tree.BaseNode.allNodes[id];
if (node) {
return node;
}
if (target == this.getElement()) {
break;
}
// Don't bubble if we hit a group. See issue #714.
if (target.getAttribute('role') == Blockly.utils.aria.Role.GROUP) {
return null;
}
target = target.parentNode;
}
return null;
};
/**
* Creates a new tree node using the same config as the root.
* @param {string=} opt_content The content of the node label.
* @return {!Blockly.tree.TreeNode} The new item.
* @package
*/
Blockly.tree.TreeControl.prototype.createNode = function(opt_content) {
return new Blockly.tree.TreeNode(
this.toolbox_, opt_content || '', this.config_);
};