diff --git a/core/components/component.js b/core/components/component.js index 2c36fbba9..93aa46ae3 100644 --- a/core/components/component.js +++ b/core/components/component.js @@ -47,7 +47,7 @@ Blockly.Component = function() { * been set by calling {@link #setRightToLeft} explicitly. * @private {?boolean} */ - this.rightToLeft_ = Blockly.Component.defaultRightToLeft_; + this.rightToLeft_ = Blockly.Component.defaultRightToLeft; /** * Unique ID of the component, lazily initialized in {@link @@ -77,42 +77,37 @@ Blockly.Component = function() { this.parent_ = null; /** - * Array of child components. Lazily initialized on first use. Must be kept - * in sync with `childIndex_`. This property is strictly private and - * must not be accessed directly outside of this class! + * Array of child components. + * Must be kept in sync with `childIndex_`. This property is strictly + * private and must not be accessed directly outside of this class! * @private {?Array.} */ - this.children_ = null; + this.children_ = []; /** * Map of child component IDs to child components. Used for constant-time - * random access to child components by ID. Lazily initialized on first use. + * random access to child components by ID. * Must be kept in sync with `children_`. This property is strictly * private and must not be accessed directly outside of this class! * * @private {?Object} */ - this.childIndex_ = null; + this.childIndex_ = {}; }; /** * The default right to left value. - * @type {?boolean} - * @private + * @type {boolean} + * @package */ -Blockly.Component.defaultRightToLeft_ = false; +Blockly.Component.defaultRightToLeft = false; /** * Errors thrown by the component. * @enum {string} */ Blockly.Component.Error = { - /** - * Error when a method is not supported. - */ - NOT_SUPPORTED: 'Method not supported', - /** * Error when the component is already rendered and another render attempt is * made. @@ -129,32 +124,7 @@ Blockly.Component.Error = { * Error when an attempt is made to add a child component at an out-of-bounds * index. We don't support sparse child arrays. */ - CHILD_INDEX_OUT_OF_BOUNDS: 'Child component index out of bounds', - - /** - * Error when an attempt is made to remove a child component from a component - * other than its parent. - */ - NOT_OUR_CHILD: 'Child is not in parent component', - - /** - * Error when an operation requiring DOM interaction is made when the - * component is not in the document - */ - NOT_IN_DOCUMENT: 'Operation not supported while component is not in document' -}; - -/** - * Set the default right-to-left value. This causes all component's created from - * this point forward to have the given value. This is useful for cases where - * a given page is always in one directionality, avoiding unnecessary - * right to left determinations. - * @param {?boolean} rightToLeft Whether the components should be rendered - * right-to-left. Null iff components should determine their directionality. - * @package - */ -Blockly.Component.setDefaultRightToLeft = function(rightToLeft) { - Blockly.Component.defaultRightToLeft_ = rightToLeft; + CHILD_INDEX_OUT_OF_BOUNDS: 'Child component index out of bounds' }; /** @@ -196,11 +166,7 @@ Blockly.Component.prototype.setElementInternal = function(element) { /** * Sets the parent of this component to use for event bubbling. Throws an error * if the component already has a parent or if an attempt is made to add a - * component to itself as a child. Callers must use `removeChild` - * or `removeChildAt` to remove components from their containers before - * calling this method. - * @see Blockly.Component#removeChild - * @see Blockly.Component#removeChildAt + * component to itself as a child. * @param {Blockly.Component} parent The parent component. * @protected */ @@ -212,8 +178,7 @@ Blockly.Component.prototype.setParent = function(parent) { if (parent && this.parent_ && this.id_ && this.parent_.getChild(this.id_) && this.parent_ != parent) { - // This component is already the child of some parent, so it should be - // removed using removeChild/removeChildAt first. + // This component is already the child of some parent. throw Error(Blockly.Component.Error.PARENT_UNABLE_TO_BE_SET); } @@ -479,12 +444,6 @@ Blockly.Component.prototype.addChildAt = function(child, index, opt_render) { throw Error(Blockly.Component.Error.CHILD_INDEX_OUT_OF_BOUNDS); } - // Create the index and the child array on first use. - if (!this.childIndex_ || !this.children_) { - this.childIndex_ = {}; - this.children_ = []; - } - // Moving child within component, remove old reference. this.childIndex_[child.getId()] = child; if (child.getParent() == this) { @@ -554,16 +513,12 @@ Blockly.Component.prototype.getContentElement = function() { * @protected */ Blockly.Component.prototype.isRightToLeft = function() { - if (this.rightToLeft_ == null) { - this.rightToLeft_ = Blockly.utils.style.isRightToLeft( - this.inDocument_ ? this.element_ : document.body); - } return this.rightToLeft_; }; /** * Set is right-to-left. This function should be used if the component needs - * to know the rendering direction during dom creation (i.e. before + * to know the rendering direction during DOM creation (i.e. before * {@link #enterDocument} is called and is right-to-left is set). * @param {boolean} rightToLeft Whether the component is rendered * right-to-left. @@ -582,7 +537,7 @@ Blockly.Component.prototype.setRightToLeft = function(rightToLeft) { * @protected */ Blockly.Component.prototype.hasChildren = function() { - return !!this.children_ && this.children_.length != 0; + return this.children_.length != 0; }; /** @@ -591,7 +546,7 @@ Blockly.Component.prototype.hasChildren = function() { * @protected */ Blockly.Component.prototype.getChildCount = function() { - return this.children_ ? this.children_.length : 0; + return this.children_.length; }; /** @@ -602,10 +557,8 @@ Blockly.Component.prototype.getChildCount = function() { */ Blockly.Component.prototype.getChild = function(id) { // Use childIndex_ for O(1) access by ID. - return (this.childIndex_ && id) ? - /** @type {Blockly.Component} */ ( - this.childIndex_[id]) || - null : null; + return id ? + /** @type {Blockly.Component} */ (this.childIndex_[id]) || null : null; }; /** @@ -616,7 +569,7 @@ Blockly.Component.prototype.getChild = function(id) { */ Blockly.Component.prototype.getChildAt = function(index) { // Use children_ for access by index. - return this.children_ ? this.children_[index] || null : null; + return this.children_[index] || null; }; /** @@ -631,10 +584,8 @@ Blockly.Component.prototype.getChildAt = function(index) { * @protected */ Blockly.Component.prototype.forEachChild = function(f, opt_obj) { - if (this.children_) { - for (var i = 0; i < this.children_.length; i++) { - f.call(/** @type {?} */ (opt_obj), this.children_[i], i); - } + for (var i = 0; i < this.children_.length; i++) { + f.call(/** @type {?} */ (opt_obj), this.children_[i], i); } }; @@ -646,96 +597,5 @@ Blockly.Component.prototype.forEachChild = function(f, opt_obj) { * @protected */ Blockly.Component.prototype.indexOfChild = function(child) { - return (this.children_ && child) ? this.children_.indexOf(child) : -1; -}; - -/** - * Removes the given child from this component, and returns it. Throws an error - * if the argument is invalid or if the specified child isn't found in the - * parent component. The argument can either be a string (interpreted as the - * ID of the child component to remove) or the child component itself. - * - * If `opt_unrender` is true, calls {@link Blockly.Component#exitDocument} - * on the removed child, and subsequently detaches the child's DOM from the - * document. Otherwise it is the caller's responsibility to clean up the child - * component's DOM. - * - * @see Blockly.Component#removeChildAt - * @param {string|Blockly.Component|null} child The ID of the child to remove, - * or the child component itself. - * @param {boolean=} opt_unrender If true, calls `exitDocument` on the - * removed child component, and detaches its DOM from the document. - * @return {Blockly.Component} The removed component, if any. - * @protected - */ -Blockly.Component.prototype.removeChild = function(child, opt_unrender) { - if (child) { - // Normalize child to be the object and id to be the ID string. This also - // ensures that the child is really ours. - var id = (typeof child === 'string' || child instanceof String) ? - String(child) : child.getId(); - child = this.getChild(id); - - if (id && child) { - delete this.childIndex_[id]; - var index = this.children_.indexOf(child); - if (index > -1) { - this.children_.splice(index, 1); - } - - if (opt_unrender) { - // Remove the child component's DOM from the document. We have to call - // exitDocument first (see documentation). - child.exitDocument(); - if (child.element_) { - Blockly.utils.dom.removeNode(child.element_); - } - } - - // Child's parent must be set to null after exitDocument is called - // so that the child can unlisten to its parent if required. - child.setParent(null); - } - } - - if (!child) { - throw Error(Blockly.Component.Error.NOT_OUR_CHILD); - } - - return /** @type {!Blockly.Component} */ (child); -}; - -/** - * Removes the child at the given index from this component, and returns it. - * Throws an error if the argument is out of bounds, or if the specified child - * isn't found in the parent. See {@link Blockly.Component#removeChild} for - * detailed semantics. - * - * @see Blockly.Component#removeChild - * @param {number} index 0-based index of the child to remove. - * @param {boolean=} opt_unrender If true, calls `exitDocument` on the - * removed child component, and detaches its DOM from the document. - * @return {Blockly.Component} The removed component, if any. - * @protected - */ -Blockly.Component.prototype.removeChildAt = function(index, opt_unrender) { - // removeChild(null) will throw error. - return this.removeChild(this.getChildAt(index), opt_unrender); -}; - -/** - * Removes every child component attached to this one and returns them. - * - * @see Blockly.Component#removeChild - * @param {boolean=} opt_unrender If true, calls {@link #exitDocument} on the - * removed child components, and detaches their DOM from the document. - * @return {!Array.} The removed components if any. - * @protected - */ -Blockly.Component.prototype.removeChildren = function(opt_unrender) { - var removedChildren = []; - while (this.hasChildren()) { - removedChildren.push(this.removeChildAt(0, opt_unrender)); - } - return removedChildren; + return this.children_.indexOf(child); }; diff --git a/core/components/menu/menuitem.js b/core/components/menu/menuitem.js index acb71d9fb..3ca493bcf 100644 --- a/core/components/menu/menuitem.js +++ b/core/components/menu/menuitem.js @@ -82,12 +82,11 @@ Blockly.MenuItem.prototype.createDom = function() { (!this.enabled_ ? 'goog-menuitem-disabled ' : '') + (this.checked_ ? 'goog-option-selected ' : '') + (this.isRightToLeft() ? 'goog-menuitem-rtl ' : '')); - element.setAttribute('style', 'user-select: none'); var content = this.getContentWrapperDom(); element.appendChild(content); - // Add a cheeckbox for checkable menu items. + // Add a checkbox for checkable menu items. var checkboxDom = this.getCheckboxDom(); if (checkboxDom) { content.appendChild(checkboxDom); @@ -113,7 +112,6 @@ Blockly.MenuItem.prototype.getCheckboxDom = function() { } var menuItemCheckbox = document.createElement('div'); menuItemCheckbox.setAttribute('class', 'goog-menuitem-checkbox'); - menuItemCheckbox.setAttribute('style', 'user-select: none;'); return menuItemCheckbox; }; @@ -136,7 +134,6 @@ Blockly.MenuItem.prototype.getContentDom = function() { Blockly.MenuItem.prototype.getContentWrapperDom = function() { var contentWrapper = document.createElement('div'); contentWrapper.setAttribute('class', 'goog-menuitem-content'); - contentWrapper.setAttribute('style', 'user-select: none;'); return contentWrapper; }; @@ -212,15 +209,6 @@ Blockly.MenuItem.prototype.setChecked = function(checked) { } }; -/** - * Returns true if the component is currently highlighted, false otherwise. - * @return {boolean} Whether the component is highlighted. - * @package - */ -Blockly.MenuItem.prototype.isHighlighted = function() { - return this.highlight_; -}; - /** * Highlights or unhighlights the component. * @param {boolean} highlight Whether to highlight or unhighlight the component. diff --git a/core/components/tree/basenode.js b/core/components/tree/basenode.js index 456cffde2..e61e932c2 100644 --- a/core/components/tree/basenode.js +++ b/core/components/tree/basenode.js @@ -233,8 +233,7 @@ Blockly.tree.BaseNode.prototype.exitDocument = function() { * The method assumes that the child doesn't have parent node yet. * @override */ -Blockly.tree.BaseNode.prototype.addChildAt = function( - child, index) { +Blockly.tree.BaseNode.prototype.addChildAt = function(child, index) { child = /** @type {Blockly.tree.BaseNode} */ (child); var prevNode = this.getChildAt(index - 1); var nextNode = this.getChildAt(index); @@ -293,21 +292,15 @@ Blockly.tree.BaseNode.prototype.addChildAt = function( }; /** - * Adds a node as a child to the current node. + * Appends a node as a child to the current node. * @param {Blockly.tree.BaseNode} child The child to add. - * @param {Blockly.tree.BaseNode=} opt_before If specified, the new child is - * added as a child before this one. If not specified, it's appended to the - * end. - * @return {!Blockly.tree.BaseNode} The added child. * @package */ -Blockly.tree.BaseNode.prototype.add = function(child, opt_before) { +Blockly.tree.BaseNode.prototype.add = function(child) { if (child.getParent()) { - child.getParent().removeChild(child); + throw Error(Blockly.Component.Error.PARENT_UNABLE_TO_BE_SET); } - this.addChildAt( - child, opt_before ? this.indexOfChild(opt_before) : this.getChildCount()); - return child; + this.addChildAt(child, this.getChildCount()); }; /** diff --git a/core/css.js b/core/css.js index 9ec99dfad..28dc29f9d 100644 --- a/core/css.js +++ b/core/css.js @@ -136,7 +136,6 @@ Blockly.Css.CONTENT = [ '.blocklyNonSelectable {', 'user-select: none;', - '-moz-user-select: none;', '-ms-user-select: none;', '-webkit-user-select: none;', '}', @@ -431,7 +430,6 @@ Blockly.Css.CONTENT = [ */ '.blocklySvg text, .blocklyBlockDragSurface text {', 'user-select: none;', - '-moz-user-select: none;', '-ms-user-select: none;', '-webkit-user-select: none;', 'cursor: inherit;', @@ -699,7 +697,6 @@ Blockly.Css.CONTENT = [ 'overflow-y: auto;', 'position: absolute;', 'user-select: none;', - '-moz-user-select: none;', '-ms-user-select: none;', '-webkit-user-select: none;', 'z-index: 70;', /* so blocks go under toolbox when dragging */ diff --git a/core/field_colour.js b/core/field_colour.js index 7dc9f4a23..62057cc95 100644 --- a/core/field_colour.js +++ b/core/field_colour.js @@ -504,7 +504,7 @@ Blockly.FieldColour.prototype.setHighlightedCell_ = function(cell, index) { if (highlighted) { Blockly.utils.dom.removeClass(highlighted, 'blocklyColourHighlighted'); } - // Highight new item. + // Highlight new item. Blockly.utils.dom.addClass(cell, 'blocklyColourHighlighted'); // Set new highlighted index. this.highlightedIndex_ = index; diff --git a/core/flyout_base.js b/core/flyout_base.js index 04d104969..2ab6ecb64 100644 --- a/core/flyout_base.js +++ b/core/flyout_base.js @@ -371,7 +371,7 @@ Blockly.Flyout.prototype.updateDisplay_ = function() { show = this.isVisible(); } this.svgGroup_.style.display = show ? 'block' : 'none'; - // Update the scrollbar's visiblity too since it should mimic the + // Update the scrollbar's visibility too since it should mimic the // flyout's visibility. this.scrollbar_.setContainerVisible(show); }; diff --git a/core/inject.js b/core/inject.js index 5885201bf..a7d49b95f 100644 --- a/core/inject.js +++ b/core/inject.js @@ -96,7 +96,7 @@ Blockly.createDom_ = function(container, options) { // then manually positions content in RTL as needed. container.setAttribute('dir', 'LTR'); // Set the default direction for Components to use. - Blockly.Component.setDefaultRightToLeft(options.RTL); + Blockly.Component.defaultRightToLeft = options.RTL; // Load CSS. Blockly.Css.inject(options.hasCss, options.pathToMedia); diff --git a/core/toolbox.js b/core/toolbox.js index 5a01affec..b32614521 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -198,6 +198,19 @@ Blockly.Toolbox.prototype.init = function() { this.config_['cleardotPath'] = workspace.options.pathToMedia + '1x1.gif'; this.config_['cssCollapsedFolderIcon'] = 'blocklyTreeIconClosed' + (workspace.RTL ? 'Rtl' : 'Ltr'); + this.renderTree(workspace.options.languageTree); +}; + +/** + * Fill the toolbox with categories and blocks. + * @param {!Node} languageTree DOM tree of blocks. + * @package + */ +Blockly.Toolbox.prototype.renderTree = function(languageTree) { + if (this.tree_) { + this.tree_.dispose(); // Delete any existing content. + this.lastCategory_ = null; + } var tree = new Blockly.tree.TreeControl(this, /** @type {!Blockly.tree.BaseNode.Config} */ (this.config_)); this.tree_ = tree; @@ -205,8 +218,18 @@ Blockly.Toolbox.prototype.init = function() { tree.onBeforeSelected(this.handleBeforeTreeSelected_); tree.onAfterSelected(this.handleAfterTreeSelected_); var openNode = null; - if (workspace.options.languageTree) { - openNode = this.populate_(workspace.options.languageTree); + if (languageTree) { + this.tree_.blocks = []; + this.hasColours_ = false; + var openNode = + this.syncTrees_(languageTree, this.tree_, this.workspace_.options.pathToMedia); + + if (this.tree_.blocks.length) { + throw Error('Toolbox cannot have both blocks and categories ' + + 'in the root level.'); + } + // Fire a resize event since the toolbox may have changed width and height. + this.workspace_.resizeContents(); } tree.render(this.HtmlDiv); if (openNode) { @@ -377,29 +400,6 @@ Blockly.Toolbox.prototype.position = function() { this.flyout_.position(); }; -/** - * Fill the toolbox with categories and blocks. - * @param {!Node} newTree DOM tree of blocks. - * @return {Blockly.tree.BaseNode} Tree node to open at startup (or null). - * @private - */ -Blockly.Toolbox.prototype.populate_ = function(newTree) { - this.tree_.removeChildren(); // Delete any existing content. - this.tree_.blocks = []; - this.hasColours_ = false; - var openNode = - this.syncTrees_(newTree, this.tree_, this.workspace_.options.pathToMedia); - - if (this.tree_.blocks.length) { - throw Error('Toolbox cannot have both blocks and categories ' + - 'in the root level.'); - } - - // Fire a resize event since the toolbox may have changed width and height. - this.workspace_.resizeContents(); - return openNode; -}; - /** * Sync trees of the toolbox. * @param {!Node} treeIn DOM tree of blocks. diff --git a/core/workspace_svg.js b/core/workspace_svg.js index cb044addb..1d291e382 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -1727,10 +1727,7 @@ Blockly.WorkspaceSvg.prototype.updateToolbox = function(tree) { throw Error('Existing toolbox has no categories. Can\'t change mode.'); } this.options.languageTree = tree; - var openNode = this.toolbox_.populate_(tree); - this.toolbox_.addColour_(); - this.toolbox_.position(); - this.toolbox_.tree_.setSelectedItem(openNode); + this.toolbox_.renderTree(tree); } else { if (!this.flyout_) { throw Error('Existing toolbox has categories. Can\'t change mode.'); @@ -1750,7 +1747,7 @@ Blockly.WorkspaceSvg.prototype.markFocused = function() { Blockly.mainWorkspace = this; // We call e.preventDefault in many event handlers which means we // need to explicitly grab focus (e.g from a textarea) because - // the browser will not do it for us. How to do this is browser dependant. + // the browser will not do it for us. How to do this is browser dependent. this.setBrowserFocus(); } }; @@ -1760,7 +1757,7 @@ Blockly.WorkspaceSvg.prototype.markFocused = function() { * @private */ Blockly.WorkspaceSvg.prototype.setBrowserFocus = function() { - // Blur whatever was focused since explcitly grabbing focus below does not + // Blur whatever was focused since explicitly grabbing focus below does not // work in Edge. if (document.activeElement) { document.activeElement.blur();