toolbox.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. /**
  2. * @license
  3. * Visual Blocks Editor
  4. *
  5. * Copyright 2011 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview Toolbox from whence to create blocks.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Toolbox');
  26. goog.require('Blockly.Flyout');
  27. goog.require('goog.dom');
  28. goog.require('goog.events');
  29. goog.require('goog.events.BrowserFeature');
  30. goog.require('goog.html.SafeHtml');
  31. goog.require('goog.html.SafeStyle');
  32. goog.require('goog.math.Rect');
  33. goog.require('goog.style');
  34. goog.require('goog.ui.tree.TreeControl');
  35. goog.require('goog.ui.tree.TreeNode');
  36. /**
  37. * Class for a Toolbox.
  38. * Creates the toolbox's DOM.
  39. * @param {!Blockly.Workspace} workspace The workspace in which to create new
  40. * blocks.
  41. * @constructor
  42. */
  43. Blockly.Toolbox = function(workspace) {
  44. /**
  45. * @type {!Blockly.Workspace}
  46. * @private
  47. */
  48. this.workspace_ = workspace;
  49. /**
  50. * Is RTL vs LTR.
  51. * @type {boolean}
  52. */
  53. this.RTL = workspace.options.RTL;
  54. /**
  55. * Whether the toolbox should be laid out horizontally.
  56. * @type {boolean}
  57. * @private
  58. */
  59. this.horizontalLayout_ = workspace.options.horizontalLayout;
  60. /**
  61. * Position of the toolbox and flyout relative to the workspace.
  62. * @type {number}
  63. */
  64. this.toolboxPosition = workspace.options.toolboxPosition;
  65. /**
  66. * Configuration constants for Closure's tree UI.
  67. * @type {Object.<string,*>}
  68. * @private
  69. */
  70. this.config_ = {
  71. indentWidth: 19,
  72. cssRoot: 'blocklyTreeRoot',
  73. cssHideRoot: 'blocklyHidden',
  74. cssItem: '',
  75. cssTreeRow: 'blocklyTreeRow',
  76. cssItemLabel: 'blocklyTreeLabel',
  77. cssTreeIcon: 'blocklyTreeIcon',
  78. cssExpandedFolderIcon: 'blocklyTreeIconOpen',
  79. cssFileIcon: 'blocklyTreeIconNone',
  80. cssSelectedRow: 'blocklyTreeSelected'
  81. };
  82. /**
  83. * Configuration constants for tree separator.
  84. * @type {Object.<string,*>}
  85. * @private
  86. */
  87. this.treeSeparatorConfig_ = {
  88. cssTreeRow: 'blocklyTreeSeparator'
  89. };
  90. if (this.horizontalLayout_) {
  91. this.config_['cssTreeRow'] =
  92. this.config_['cssTreeRow'] +
  93. (workspace.RTL ?
  94. ' blocklyHorizontalTreeRtl' : ' blocklyHorizontalTree');
  95. this.treeSeparatorConfig_['cssTreeRow'] =
  96. 'blocklyTreeSeparatorHorizontal ' +
  97. (workspace.RTL ?
  98. 'blocklyHorizontalTreeRtl' : 'blocklyHorizontalTree');
  99. this.config_['cssTreeIcon'] = '';
  100. }
  101. };
  102. /**
  103. * Width of the toolbox, which changes only in vertical layout.
  104. * @type {number}
  105. */
  106. Blockly.Toolbox.prototype.width = 0;
  107. /**
  108. * Height of the toolbox, which changes only in horizontal layout.
  109. * @type {number}
  110. */
  111. Blockly.Toolbox.prototype.height = 0;
  112. /**
  113. * The SVG group currently selected.
  114. * @type {SVGGElement}
  115. * @private
  116. */
  117. Blockly.Toolbox.prototype.selectedOption_ = null;
  118. /**
  119. * The tree node most recently selected.
  120. * @type {goog.ui.tree.BaseNode}
  121. * @private
  122. */
  123. Blockly.Toolbox.prototype.lastCategory_ = null;
  124. /**
  125. * Initializes the toolbox.
  126. */
  127. Blockly.Toolbox.prototype.init = function() {
  128. var workspace = this.workspace_;
  129. var svg = this.workspace_.getParentSvg();
  130. // Create an HTML container for the Toolbox menu.
  131. this.HtmlDiv = goog.dom.createDom('div', 'blocklyToolboxDiv');
  132. this.HtmlDiv.setAttribute('dir', workspace.RTL ? 'RTL' : 'LTR');
  133. // For Blockscad, position the toolbox inside the blocklyDiv
  134. svg.parentNode.insertBefore(this.HtmlDiv, svg);
  135. //document.body.appendChild(this.HtmlDiv);
  136. // Clicking on toolbar closes popups.
  137. Blockly.bindEvent_(this.HtmlDiv, 'mousedown', this,
  138. function(e) {
  139. if (Blockly.isRightButton(e) || e.target == this.HtmlDiv) {
  140. // Close flyout.
  141. Blockly.hideChaff(false);
  142. } else {
  143. // Just close popups.
  144. Blockly.hideChaff(true);
  145. }
  146. });
  147. var workspaceOptions = {
  148. disabledPatternId: workspace.options.disabledPatternId,
  149. parentWorkspace: workspace,
  150. RTL: workspace.RTL,
  151. horizontalLayout: workspace.horizontalLayout,
  152. toolboxPosition: workspace.options.toolboxPosition
  153. };
  154. /**
  155. * @type {!Blockly.Flyout}
  156. * @private
  157. */
  158. this.flyout_ = new Blockly.Flyout(workspaceOptions);
  159. goog.dom.insertSiblingAfter(this.flyout_.createDom(), workspace.svgGroup_);
  160. this.flyout_.init(workspace);
  161. this.config_['cleardotPath'] = workspace.options.pathToMedia + '1x1.gif';
  162. this.config_['cssCollapsedFolderIcon'] =
  163. 'blocklyTreeIconClosed' + (workspace.RTL ? 'Rtl' : 'Ltr');
  164. var tree = new Blockly.Toolbox.TreeControl(this, this.config_);
  165. this.tree_ = tree;
  166. tree.setShowRootNode(false);
  167. tree.setShowLines(false);
  168. tree.setShowExpandIcons(false);
  169. tree.setSelectedItem(null);
  170. this.populate_(workspace.options.languageTree);
  171. tree.render(this.HtmlDiv);
  172. this.addColour_();
  173. this.position();
  174. };
  175. /**
  176. * Dispose of this toolbox.
  177. */
  178. Blockly.Toolbox.prototype.dispose = function() {
  179. this.flyout_.dispose();
  180. this.tree_.dispose();
  181. goog.dom.removeNode(this.HtmlDiv);
  182. this.workspace_ = null;
  183. this.lastCategory_ = null;
  184. };
  185. /**
  186. * Get the width of the toolbox.
  187. * @return {number} The width of the toolbox.
  188. */
  189. Blockly.Toolbox.prototype.getWidth = function() {
  190. return this.width;
  191. };
  192. /**
  193. * Get the height of the toolbox.
  194. * @return {number} The width of the toolbox.
  195. */
  196. Blockly.Toolbox.prototype.getHeight = function() {
  197. return this.height;
  198. };
  199. /**
  200. * Move the toolbox to the edge.
  201. */
  202. Blockly.Toolbox.prototype.position = function() {
  203. var treeDiv = this.HtmlDiv;
  204. if (!treeDiv) {
  205. // Not initialized yet.
  206. return;
  207. }
  208. var svg = this.workspace_.getParentSvg();
  209. var svgPosition = goog.style.getPageOffset(svg);
  210. var svgSize = Blockly.svgSize(svg);
  211. if (this.horizontalLayout_) {
  212. treeDiv.style.left = svgPosition.x + 'px';
  213. treeDiv.style.height = 'auto';
  214. treeDiv.style.width = svgSize.width + 'px';
  215. this.height = treeDiv.offsetHeight;
  216. // for Blockscad: change svgPosition.y to 0 because we attach to the blocklyDiv
  217. if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { // Top
  218. treeDiv.style.top = 0 + 'px';
  219. } else { // Bottom
  220. var topOfToolbox = 0 + svgSize.height - treeDiv.offsetHeight;
  221. treeDiv.style.top = topOfToolbox + 'px';
  222. }
  223. } else {
  224. if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { // Right
  225. treeDiv.style.left =
  226. (svgPosition.x + svgSize.width - treeDiv.offsetWidth) + 'px';
  227. } else { // Left
  228. treeDiv.style.left = svgPosition.x + 'px';
  229. }
  230. treeDiv.style.height = svgSize.height + 'px';
  231. treeDiv.style.top = 0 + 'px';
  232. this.width = treeDiv.offsetWidth;
  233. if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
  234. // For some reason the LTR toolbox now reports as 1px too wide.
  235. this.width -= 1;
  236. }
  237. }
  238. this.flyout_.position();
  239. };
  240. /**
  241. * Fill the toolbox with categories and blocks.
  242. * @param {Node} newTree DOM tree of blocks, or null.
  243. * @private
  244. */
  245. Blockly.Toolbox.prototype.populate_ = function(newTree) {
  246. this.tree_.removeChildren(); // Delete any existing content.
  247. this.tree_.blocks = [];
  248. this.hasColours_ = false;
  249. this.syncTrees_(newTree, this.tree_, this.workspace_.options.pathToMedia);
  250. if (this.tree_.blocks.length) {
  251. throw 'Toolbox cannot have both blocks and categories in the root level.';
  252. }
  253. // Fire a resize event since the toolbox may have changed width and height.
  254. Blockly.resizeSvgContents(this.workspace_);
  255. };
  256. /**
  257. * Sync trees of the toolbox.
  258. * @param {Node} treeIn DOM tree of blocks, or null.
  259. * @param {Blockly.Toolbox.TreeControl} treeOut
  260. * @param {string} pathToMedia
  261. * @private
  262. */
  263. Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
  264. var lastElement = null;
  265. for (var i = 0, childIn; childIn = treeIn.childNodes[i]; i++) {
  266. if (!childIn.tagName) {
  267. // Skip over text.
  268. continue;
  269. }
  270. switch (childIn.tagName.toUpperCase()) {
  271. case 'CATEGORY':
  272. var childOut = this.tree_.createNode(childIn.getAttribute('name'));
  273. childOut.blocks = [];
  274. treeOut.add(childOut);
  275. var custom = childIn.getAttribute('custom');
  276. if (custom) {
  277. // Variables and procedures are special dynamic categories.
  278. childOut.blocks = custom;
  279. } else {
  280. this.syncTrees_(childIn, childOut, pathToMedia);
  281. }
  282. var colour = childIn.getAttribute('colour');
  283. if (goog.isString(colour)) {
  284. if (colour.match(/^#[0-9a-fA-F]{6}$/)) {
  285. childOut.hexColour = colour;
  286. } else {
  287. childOut.hexColour = Blockly.hueToRgb(colour);
  288. }
  289. this.hasColours_ = true;
  290. } else {
  291. childOut.hexColour = '';
  292. }
  293. if (childIn.getAttribute('expanded') == 'true') {
  294. if (childOut.blocks.length) {
  295. this.tree_.setSelectedItem(childOut);
  296. }
  297. childOut.setExpanded(true);
  298. } else {
  299. childOut.setExpanded(false);
  300. }
  301. lastElement = childIn;
  302. // for BLOCKSCAD: populate the list of category ids for css
  303. Blockscad.Toolbox.catIDs.push(childOut.id_);
  304. break;
  305. case 'SEP':
  306. if (lastElement) {
  307. if (lastElement.tagName.toUpperCase() == 'CATEGORY') {
  308. // Separator between two categories.
  309. // <sep></sep>
  310. treeOut.add(new Blockly.Toolbox.TreeSeparator(
  311. this.treeSeparatorConfig_));
  312. } else {
  313. // Change the gap between two blocks.
  314. // <sep gap="36"></sep>
  315. // The default gap is 24, can be set larger or smaller.
  316. // Note that a deprecated method is to add a gap to a block.
  317. // <block type="math_arithmetic" gap="8"></block>
  318. var newGap = parseFloat(childIn.getAttribute('gap'));
  319. if (!isNaN(newGap)) {
  320. var oldGap = parseFloat(lastElement.getAttribute('gap'));
  321. var gap = isNaN(oldGap) ? newGap : oldGap + newGap;
  322. lastElement.setAttribute('gap', gap);
  323. }
  324. }
  325. }
  326. break;
  327. case 'BLOCK':
  328. case 'SHADOW':
  329. treeOut.blocks.push(childIn);
  330. lastElement = childIn;
  331. break;
  332. }
  333. }
  334. };
  335. /**
  336. * Recursively add colours to this toolbox.
  337. * @param {Blockly.Toolbox.TreeNode} opt_tree Starting point of tree.
  338. * Defaults to the root node.
  339. * @private
  340. */
  341. Blockly.Toolbox.prototype.addColour_ = function(opt_tree) {
  342. var tree = opt_tree || this.tree_;
  343. var children = tree.getChildren();
  344. for (var i = 0, child; child = children[i]; i++) {
  345. var element = child.getRowElement();
  346. if (element) {
  347. if (this.hasColours_) {
  348. var border = '8px solid ' + (child.hexColour || '#ddd');
  349. } else {
  350. var border = 'none';
  351. }
  352. if (this.workspace_.RTL) {
  353. element.style.borderRight = border;
  354. } else {
  355. element.style.borderLeft = border;
  356. }
  357. }
  358. this.addColour_(child);
  359. }
  360. };
  361. /**
  362. * Unhighlight any previously specified option.
  363. */
  364. Blockly.Toolbox.prototype.clearSelection = function() {
  365. this.tree_.setSelectedItem(null);
  366. };
  367. /**
  368. * Return the deletion rectangle for this toolbox.
  369. * @return {goog.math.Rect} Rectangle in which to delete.
  370. */
  371. Blockly.Toolbox.prototype.getClientRect = function() {
  372. if (!this.HtmlDiv) {
  373. return null;
  374. }
  375. // BIG_NUM is offscreen padding so that blocks dragged beyond the toolbox
  376. // area are still deleted. Must be smaller than Infinity, but larger than
  377. // the largest screen size.
  378. var BIG_NUM = 10000000;
  379. var toolboxRect = this.HtmlDiv.getBoundingClientRect();
  380. var x = toolboxRect.left;
  381. var y = toolboxRect.top;
  382. var width = toolboxRect.width;
  383. var height = toolboxRect.height;
  384. // Assumes that the toolbox is on the SVG edge. If this changes
  385. // (e.g. toolboxes in mutators) then this code will need to be more complex.
  386. if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
  387. return new goog.math.Rect(-BIG_NUM, -BIG_NUM, BIG_NUM + x + width,
  388. 2 * BIG_NUM);
  389. } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
  390. return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width, 2 * BIG_NUM);
  391. } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) {
  392. return new goog.math.Rect(-BIG_NUM, -BIG_NUM, 2 * BIG_NUM,
  393. BIG_NUM + y + height);
  394. } else { // Bottom
  395. return new goog.math.Rect(0, y, 2 * BIG_NUM, BIG_NUM + width);
  396. }
  397. };
  398. // Extending Closure's Tree UI.
  399. /**
  400. * Extention of a TreeControl object that uses a custom tree node.
  401. * @param {Blockly.Toolbox} toolbox The parent toolbox for this tree.
  402. * @param {Object} config The configuration for the tree. See
  403. * goog.ui.tree.TreeControl.DefaultConfig.
  404. * @constructor
  405. * @extends {goog.ui.tree.TreeControl}
  406. */
  407. Blockly.Toolbox.TreeControl = function(toolbox, config) {
  408. this.toolbox_ = toolbox;
  409. goog.ui.tree.TreeControl.call(this, goog.html.SafeHtml.EMPTY, config);
  410. };
  411. goog.inherits(Blockly.Toolbox.TreeControl, goog.ui.tree.TreeControl);
  412. /**
  413. * Adds touch handling to TreeControl.
  414. * @override
  415. */
  416. Blockly.Toolbox.TreeControl.prototype.enterDocument = function() {
  417. Blockly.Toolbox.TreeControl.superClass_.enterDocument.call(this);
  418. // Add touch handler.
  419. if (goog.events.BrowserFeature.TOUCH_ENABLED) {
  420. var el = this.getElement();
  421. Blockly.bindEvent_(el, goog.events.EventType.TOUCHSTART, this,
  422. this.handleTouchEvent_);
  423. }
  424. };
  425. /**
  426. * Handles touch events.
  427. * @param {!goog.events.BrowserEvent} e The browser event.
  428. * @private
  429. */
  430. Blockly.Toolbox.TreeControl.prototype.handleTouchEvent_ = function(e) {
  431. e.preventDefault();
  432. var node = this.getNodeFromEvent_(e);
  433. if (node && e.type === goog.events.EventType.TOUCHSTART) {
  434. // Fire asynchronously since onMouseDown takes long enough that the browser
  435. // would fire the default mouse event before this method returns.
  436. setTimeout(function() {
  437. node.onMouseDown(e); // Same behaviour for click and touch.
  438. }, 1);
  439. }
  440. };
  441. /**
  442. * Creates a new tree node using a custom tree node.
  443. * @param {string=} opt_html The HTML content of the node label.
  444. * @return {!goog.ui.tree.TreeNode} The new item.
  445. * @override
  446. */
  447. Blockly.Toolbox.TreeControl.prototype.createNode = function(opt_html) {
  448. return new Blockly.Toolbox.TreeNode(this.toolbox_, opt_html ?
  449. goog.html.SafeHtml.htmlEscape(opt_html) : goog.html.SafeHtml.EMPTY,
  450. this.getConfig(), this.getDomHelper());
  451. };
  452. /**
  453. * Display/hide the flyout when an item is selected.
  454. * @param {goog.ui.tree.BaseNode} node The item to select.
  455. * @override
  456. */
  457. Blockly.Toolbox.TreeControl.prototype.setSelectedItem = function(node) {
  458. var toolbox = this.toolbox_;
  459. if (node == this.selectedItem_ || node == toolbox.tree_) {
  460. return;
  461. }
  462. if (toolbox.lastCategory_) {
  463. toolbox.lastCategory_.getRowElement().style.backgroundColor = '';
  464. }
  465. // if (node) {
  466. // var hexColour = node.hexColour || '#57e';
  467. // node.getRowElement().style.backgroundColor = hexColour;
  468. // // Add colours to child nodes which may have been collapsed and thus
  469. // // not rendered.
  470. // toolbox.addColour_(node);
  471. // }
  472. var oldNode = this.getSelectedItem();
  473. goog.ui.tree.TreeControl.prototype.setSelectedItem.call(this, node);
  474. if (node && node.blocks && node.blocks.length) {
  475. toolbox.flyout_.show(node.blocks);
  476. // Scroll the flyout to the top if the category has changed.
  477. if (toolbox.lastCategory_ != node) {
  478. toolbox.flyout_.scrollToStart();
  479. }
  480. } else {
  481. // Hide the flyout.
  482. toolbox.flyout_.hide();
  483. }
  484. if (oldNode != node && oldNode != this) {
  485. var event = new Blockly.Events.Ui(null, 'category',
  486. oldNode && oldNode.getHtml(), node && node.getHtml());
  487. event.workspaceId = toolbox.workspace_.id;
  488. Blockly.Events.fire(event);
  489. }
  490. if (node) {
  491. toolbox.lastCategory_ = node;
  492. }
  493. };
  494. /**
  495. * A single node in the tree, customized for Blockly's UI.
  496. * @param {Blockly.Toolbox} toolbox The parent toolbox for this tree.
  497. * @param {!goog.html.SafeHtml} html The HTML content of the node label.
  498. * @param {Object=} opt_config The configuration for the tree. See
  499. * goog.ui.tree.TreeControl.DefaultConfig. If not specified, a default config
  500. * will be used.
  501. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
  502. * @constructor
  503. * @extends {goog.ui.tree.TreeNode}
  504. */
  505. Blockly.Toolbox.TreeNode = function(toolbox, html, opt_config, opt_domHelper) {
  506. goog.ui.tree.TreeNode.call(this, html, opt_config, opt_domHelper);
  507. if (toolbox) {
  508. var resize = function() {
  509. // Even though the div hasn't changed size, the visible workspace
  510. // surface of the workspace has, so we may need to reposition everything.
  511. Blockly.svgResize(toolbox.workspace_);
  512. };
  513. // Fire a resize event since the toolbox may have changed width.
  514. goog.events.listen(toolbox.tree_,
  515. goog.ui.tree.BaseNode.EventType.EXPAND, resize);
  516. goog.events.listen(toolbox.tree_,
  517. goog.ui.tree.BaseNode.EventType.COLLAPSE, resize);
  518. }
  519. };
  520. goog.inherits(Blockly.Toolbox.TreeNode, goog.ui.tree.TreeNode);
  521. /**
  522. * Supress population of the +/- icon.
  523. * @return {!goog.html.SafeHtml} The source for the icon.
  524. * @override
  525. */
  526. Blockly.Toolbox.TreeNode.prototype.getExpandIconSafeHtml = function() {
  527. return goog.html.SafeHtml.create('span');
  528. };
  529. /**
  530. * Expand or collapse the node on mouse click.
  531. * @param {!goog.events.BrowserEvent} e The browser event.
  532. * @override
  533. */
  534. Blockly.Toolbox.TreeNode.prototype.onMouseDown = function(e) {
  535. // Expand icon.
  536. if (this.hasChildren() && this.isUserCollapsible_) {
  537. this.toggle();
  538. this.select();
  539. } else if (this.isSelected()) {
  540. this.getTree().setSelectedItem(null);
  541. } else {
  542. this.select();
  543. }
  544. this.updateRow();
  545. };
  546. /**
  547. * Supress the inherited double-click behaviour.
  548. * @param {!goog.events.BrowserEvent} e The browser event.
  549. * @override
  550. * @private
  551. */
  552. Blockly.Toolbox.TreeNode.prototype.onDoubleClick_ = function(e) {
  553. // NOP.
  554. };
  555. /**
  556. * A blank separator node in the tree.
  557. * @param {Object=} config The configuration for the tree. See
  558. * goog.ui.tree.TreeControl.DefaultConfig. If not specified, a default config
  559. * will be used.
  560. * @constructor
  561. * @extends {Blockly.Toolbox.TreeNode}
  562. */
  563. Blockly.Toolbox.TreeSeparator = function(config) {
  564. Blockly.Toolbox.TreeNode.call(this, null, '', config);
  565. };
  566. goog.inherits(Blockly.Toolbox.TreeSeparator, Blockly.Toolbox.TreeNode);