mirror of
https://github.com/google/blockly.git
synced 2026-01-08 01:20:12 +01:00
333 lines
8.1 KiB
JavaScript
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_);
|
|
};
|