From 2224bb2a4efda53d3c4f6b12c21baf11a3155273 Mon Sep 17 00:00:00 2001 From: Sam El-Husseini Date: Tue, 15 Oct 2019 17:56:53 -0500 Subject: [PATCH] More field dispose (#3201) * Dispose of element references in fields. * Fewer warnings --- core/blockly.js | 11 ++++- core/components/component.js | 2 +- core/field.js | 95 +++++++++++++++++++++++++++--------- core/field_angle.js | 45 +++++++++++++++-- core/field_colour.js | 84 ++++++++++++++++++++++++++----- core/field_dropdown.js | 63 ++++++++++++++++-------- core/field_image.js | 26 ++++++---- core/field_multilineinput.js | 27 ++++++---- core/field_textinput.js | 37 +++++++++++--- core/field_variable.js | 3 +- 10 files changed, 308 insertions(+), 85 deletions(-) diff --git a/core/blockly.js b/core/blockly.js index e4e8203c0..c56224ba3 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -113,6 +113,13 @@ Blockly.clipboardTypeCounts_ = null; */ Blockly.cache3dSupported_ = null; +/** + * Blockly opaque event data used to unbind events when using + * `Blockly.bindEvent_` and `Blockly.bindEventWithChecks_`. + * @typedef {!Array.} + */ +Blockly.EventData; + /** * Returns the dimensions of the specified SVG image. * @param {!Element} svg SVG image. @@ -455,7 +462,7 @@ Blockly.defineBlocksWithJsonArray = function(jsonArray) { * should prevent the default handler. False by default. If * opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be * provided. - * @return {!Array.} Opaque data that can be passed to unbindEvent_. + * @return {!Blockly.EventData} Opaque data that can be passed to unbindEvent_. */ Blockly.bindEventWithChecks_ = function(node, name, thisObject, func, opt_noCaptureIdentifier, opt_noPreventDefault) { @@ -521,7 +528,7 @@ Blockly.bindEventWithChecks_ = function(node, name, thisObject, func, * @param {string} name Event name to listen to (e.g. 'mousedown'). * @param {Object} thisObject The value of 'this' in the function. * @param {!Function} func Function to call when event is triggered. - * @return {!Array.} Opaque data that can be passed to unbindEvent_. + * @return {!Blockly.EventData} Opaque data that can be passed to unbindEvent_. */ Blockly.bindEvent_ = function(node, name, thisObject, func) { var wrapFunc = function(e) { diff --git a/core/components/component.js b/core/components/component.js index 82507405c..86e55fab8 100644 --- a/core/components/component.js +++ b/core/components/component.js @@ -339,7 +339,7 @@ Blockly.Component.prototype.exitDocument = function() { /** * Disposes of the object. If the object hasn't already been disposed of, calls * {@link #disposeInternal}. - * @protected + * @package */ Blockly.Component.prototype.dispose = function() { if (!this.disposed_) { diff --git a/core/field.js b/core/field.js index a9d7b919c..ee88f82f7 100644 --- a/core/field.js +++ b/core/field.js @@ -94,6 +94,41 @@ Blockly.Field = function(value, opt_validator, opt_config) { */ this.markerSvg_ = null; + /** + * The rendered field's SVG group element. + * @type {SVGGElement} + * @protected + */ + this.fieldGroup_ = null; + + /** + * The rendered field's SVG border element. + * @type {SVGRectElement} + * @protected + */ + this.borderRect_ = null; + + /** + * The rendered field's SVG text element. + * @type {SVGTextElement} + * @protected + */ + this.textElement_ = null; + + /** + * The rendered field's text content element. + * @type {Text} + * @protected + */ + this.textContent_ = null; + + /** + * Mouse down event listener data. + * @type {?Blockly.EventData} + * @private + */ + this.mouseDownWrapper_ = null; + opt_config && this.configure_(opt_config); this.setValue(value); opt_validator && this.setValidator(opt_validator); @@ -186,11 +221,21 @@ Blockly.Field.prototype.clickTarget_ = null; * A developer hook to override the returned text of this field. * Override if the text representation of the value of this field * is not just a string cast of its value. + * Return null to resort to a string cast. * @return {?string} Current text. Return null to resort to a string cast. * @protected */ Blockly.Field.prototype.getText_; +/** + * An optional method that can be defined to show an editor when the field is + * clicked. Blockly will automatically set the field as clickable if this + * method is defined. + * @return {void} + * @protected + */ +Blockly.Field.prototype.showEditor_; + /** * Non-breaking space. * @const @@ -260,11 +305,13 @@ Blockly.Field.prototype.init = function() { // Field has already been initialized once. return; } - this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null); + this.fieldGroup_ = /** @type {!SVGGElement} **/ + (Blockly.utils.dom.createSvgElement('g', {}, null)); if (!this.isVisible()) { this.fieldGroup_.style.display = 'none'; } - this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); + var sourceBlockSvg = /** @type {!Blockly.BlockSvg} **/ (this.sourceBlock_); + sourceBlockSvg.getSvgRoot().appendChild(this.fieldGroup_); this.initView(); this.updateEditable(); this.setTooltip(this.tooltip_); @@ -300,15 +347,16 @@ Blockly.Field.prototype.createBorderRect_ = function() { Math.max(this.size_.height, Blockly.Field.BORDER_RECT_DEFAULT_HEIGHT); this.size_.width = Math.max(this.size_.width, Blockly.Field.X_PADDING); - this.borderRect_ = Blockly.utils.dom.createSvgElement('rect', - { - 'rx': 4, - 'ry': 4, - 'x': 0, - 'y': 0, - 'height': this.size_.height, - 'width': this.size_.width - }, this.fieldGroup_); + this.borderRect_ = /** @type {!SVGRectElement} **/ + (Blockly.utils.dom.createSvgElement('rect', + { + 'rx': 4, + 'ry': 4, + 'x': 0, + 'y': 0, + 'height': this.size_.height, + 'width': this.size_.width + }, this.fieldGroup_)); }; /** @@ -319,13 +367,14 @@ Blockly.Field.prototype.createBorderRect_ = function() { */ Blockly.Field.prototype.createTextElement_ = function() { var xOffset = this.borderRect_ ? Blockly.Field.DEFAULT_TEXT_OFFSET : 0; - this.textElement_ = Blockly.utils.dom.createSvgElement('text', - { - 'class': 'blocklyText', - // The y position is the baseline of the text. - 'y': Blockly.Field.TEXT_DEFAULT_HEIGHT, - 'x': xOffset - }, this.fieldGroup_); + this.textElement_ = /** @type {!SVGTextElement} **/ + (Blockly.utils.dom.createSvgElement('text', + { + 'class': 'blocklyText', + // The y position is the baseline of the text. + 'y': Blockly.Field.TEXT_DEFAULT_HEIGHT, + 'x': xOffset + }, this.fieldGroup_)); this.textContent_ = document.createTextNode(''); this.textElement_.appendChild(this.textContent_); }; @@ -534,10 +583,10 @@ Blockly.Field.prototype.callValidator = function(text) { /** * Gets the group element for this editable field. * Used for measuring the size and for positioning. - * @return {!SVGElement} The group element. + * @return {!SVGGElement} The group element. */ Blockly.Field.prototype.getSvgRoot = function() { - return /** @type {!SVGElement} */ (this.fieldGroup_); + return /** @type {!SVGGElement} */ (this.fieldGroup_); }; /** @@ -582,7 +631,8 @@ Blockly.Field.prototype.updateWidth = function() { * @protected */ Blockly.Field.prototype.updateSize_ = function() { - var textWidth = Blockly.utils.dom.getTextWidth(this.textElement_); + var textWidth = Blockly.utils.dom.getTextWidth( + /** @type {!SVGTextElement} */ (this.textElement_)); var totalWidth = textWidth; if (this.borderRect_) { totalWidth += Blockly.Field.X_PADDING; @@ -881,7 +931,8 @@ Blockly.Field.prototype.getClickTarget_ = function() { * @private */ Blockly.Field.prototype.getAbsoluteXY_ = function() { - return Blockly.utils.style.getPageOffset(this.borderRect_); + return Blockly.utils.style.getPageOffset( + /** @type {!SVGRectElement} */ (this.borderRect_)); }; /** diff --git a/core/field_angle.js b/core/field_angle.js index 415662a08..b7002a495 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -83,6 +83,39 @@ Blockly.FieldAngle = function(opt_value, opt_validator, opt_config) { Blockly.FieldAngle.superClass_.constructor.call( this, opt_value || 0, opt_validator, opt_config); + + /** + * The angle picker's gauge path depending on the value. + * @type {SVGElement} + */ + this.gauge_ = null; + + /** + * The angle picker's line drawn representing the value's angle. + * @type {SVGElement} + */ + this.line_ = null; + + /** + * Wrapper click event data. + * @type {?Blockly.EventData} + * @private + */ + this.clickWrapper_ = null; + + /** + * Surface click event data. + * @type {?Blockly.EventData} + * @private + */ + this.clickSurfaceWrapper_ = null; + + /** + * Surface mouse move event data. + * @type {?Blockly.EventData} + * @private + */ + this.moveSurfaceWrapper_ = null; }; Blockly.utils.object.inherits(Blockly.FieldAngle, Blockly.FieldTextInput); @@ -309,9 +342,15 @@ Blockly.FieldAngle.prototype.dropdownCreate_ = function() { * @private */ Blockly.FieldAngle.prototype.dropdownDispose_ = function() { - Blockly.unbindEvent_(this.clickWrapper_); - Blockly.unbindEvent_(this.clickSurfaceWrapper_); - Blockly.unbindEvent_(this.moveSurfaceWrapper_); + if (this.clickWrapper_) { + Blockly.unbindEvent_(this.clickWrapper_); + } + if (this.clickSurfaceWrapper_) { + Blockly.unbindEvent_(this.clickSurfaceWrapper_); + } + if (this.moveSurfaceWrapper_) { + Blockly.unbindEvent_(this.moveSurfaceWrapper_); + } this.gauge_ = null; this.line_ = null; }; diff --git a/core/field_colour.js b/core/field_colour.js index abc481243..2e6913a68 100644 --- a/core/field_colour.js +++ b/core/field_colour.js @@ -65,6 +65,55 @@ Blockly.FieldColour = function(opt_value, opt_validator, opt_config) { */ this.size_ = new Blockly.utils.Size(Blockly.FieldColour.DEFAULT_WIDTH, Blockly.FieldColour.DEFAULT_HEIGHT); + + /** + * The field's colour picker element. + * @type {Element} + * @private + */ + this.picker_ = null; + + /** + * Index of the currently highlighted element. + * @type {?number} + * @private + */ + this.highlightedIndex_ = null; + + /** + * Mouse click event data. + * @type {?Blockly.EventData} + * @private + */ + this.onClickWrapper_ = null; + + /** + * Mouse move event data. + * @type {?Blockly.EventData} + * @private + */ + this.onMouseMoveWrapper_ = null; + + /** + * Mouse enter event data. + * @type {?Blockly.EventData} + * @private + */ + this.onMouseEnterWrapper_ = null; + + /** + * Mouse leave event data. + * @type {?Blockly.EventData} + * @private + */ + this.onMouseLeaveWrapper_ = null; + + /** + * Key down event data. + * @type {?Blockly.EventData} + * @private + */ + this.onKeyDownWrapper_ = null; }; Blockly.utils.object.inherits(Blockly.FieldColour, Blockly.Field); @@ -160,7 +209,7 @@ Blockly.FieldColour.prototype.configure_ = function(config) { */ Blockly.FieldColour.prototype.initView = function() { this.createBorderRect_(); - this.borderRect_.style['fillOpacity'] = 1; + this.borderRect_.style['fillOpacity'] = '1'; this.borderRect_.style.fill = this.value_; }; @@ -415,7 +464,7 @@ Blockly.FieldColour.prototype.moveHighlightBy_ = function(dx, dy) { } // Move the highlight to the new coordinates. - var cell = this.picker_.childNodes[y].childNodes[x]; + var cell = /** @type {!Element} */ (this.picker_.childNodes[y].childNodes[x]); var index = (y * columns) + x; this.setHighlightedCell_(cell, index); }; @@ -427,9 +476,9 @@ Blockly.FieldColour.prototype.moveHighlightBy_ = function(dx, dy) { */ Blockly.FieldColour.prototype.onMouseMove_ = function(e) { var cell = /** @type {!Element} */ (e.target); - var index = cell && cell.getAttribute('data-index'); + var index = cell && Number(cell.getAttribute('data-index')); if (index !== null && index !== this.highlightedIndex_) { - this.setHighlightedCell_(cell, Number(index)); + this.setHighlightedCell_(cell, index); } }; @@ -456,7 +505,7 @@ Blockly.FieldColour.prototype.onMouseLeave_ = function() { /** * Returns the currently highlighted item (if any). - * @return {Element} Highlighted item (null if none). + * @return {HTMLElement} Highlighted item (null if none). * @private */ Blockly.FieldColour.prototype.getHighlighted_ = function() { @@ -467,7 +516,7 @@ Blockly.FieldColour.prototype.getHighlighted_ = function() { if (!row) { return null; } - var col = row.childNodes[x]; + var col = /** @type {HTMLElement} */ (row.childNodes[x]); return col; }; @@ -489,7 +538,7 @@ Blockly.FieldColour.prototype.setHighlightedCell_ = function(cell, index) { this.highlightedIndex_ = index; // Update accessibility roles. - Blockly.utils.aria.setState(this.picker_, + Blockly.utils.aria.setState(/** @type {!Element} */ (this.picker_), Blockly.utils.aria.State.ACTIVEDESCENDANT, cell.getAttribute('id')); }; @@ -560,12 +609,23 @@ Blockly.FieldColour.prototype.dropdownCreate_ = function() { * @private */ Blockly.FieldColour.prototype.dropdownDispose_ = function() { - Blockly.unbindEvent_(this.onClickWrapper_); - Blockly.unbindEvent_(this.onMouseMoveWrapper_); - Blockly.unbindEvent_(this.onMouseEnterWrapper_); - Blockly.unbindEvent_(this.onMouseLeaveWrapper_); - Blockly.unbindEvent_(this.onKeyDownWrapper_); + if (this.onClickWrapper_) { + Blockly.unbindEvent_(this.onClickWrapper_); + } + if (this.onMouseMoveWrapper_) { + Blockly.unbindEvent_(this.onMouseMoveWrapper_); + } + if (this.onMouseEnterWrapper_) { + Blockly.unbindEvent_(this.onMouseEnterWrapper_); + } + if (this.onMouseLeaveWrapper_) { + Blockly.unbindEvent_(this.onMouseLeaveWrapper_); + } + if (this.onKeyDownWrapper_) { + Blockly.unbindEvent_(this.onKeyDownWrapper_); + } this.picker_ = null; + this.highlightedIndex_ = null; }; /** diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 238eb7f07..d01a89c1a 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -91,19 +91,33 @@ Blockly.FieldDropdown = function(menuGenerator, opt_validator, opt_config) { Blockly.FieldDropdown.superClass_.constructor.call( this, firstTuple[1], opt_validator, opt_config); - /** - * SVG image element if currently selected option is an image, or null. - * @type {SVGElement} - * @private - */ - this.imageElement_ = null; - /** * A reference to the currently selected menu item. * @type {Blockly.MenuItem} * @private */ this.selectedMenuItem_ = null; + + /** + * The dropdown menu. + * @type {Blockly.Menu} + * @private + */ + this.menu_ = null; + + /** + * SVG image element if currently selected option is an image, or null. + * @type {SVGImageElement} + * @private + */ + this.imageElement_ = null; + + /** + * SVG arrow element. + * @type {SVGTSpanElement} + * @private + */ + this.arrow_ = null; }; Blockly.utils.object.inherits(Blockly.FieldDropdown, Blockly.Field); @@ -182,12 +196,15 @@ Blockly.FieldDropdown.prototype.CURSOR = 'default'; Blockly.FieldDropdown.prototype.initView = function() { Blockly.FieldDropdown.superClass_.initView.call(this); - this.imageElement_ = Blockly.utils.dom.createSvgElement( 'image', - { - 'y': Blockly.FieldDropdown.IMAGE_Y_OFFSET - }, this.fieldGroup_); + this.imageElement_ = /** @type {!SVGImageElement} */ + (Blockly.utils.dom.createSvgElement('image', + { + 'y': Blockly.FieldDropdown.IMAGE_Y_OFFSET + }, this.fieldGroup_)); - this.arrow_ = Blockly.utils.dom.createSvgElement('tspan', {}, this.textElement_); + this.arrow_ = /** @type {!SVGTSpanElement} */ + (Blockly.utils.dom.createSvgElement('tspan', + {}, this.textElement_)); this.arrow_.appendChild(document.createTextNode( this.sourceBlock_.RTL ? Blockly.FieldDropdown.ARROW_CHAR + ' ' : @@ -228,7 +245,7 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() { /** * Create the dropdown editor. - * @return {Blockly.Menu} The newly created dropdown menu. + * @return {!Blockly.Menu} The newly created dropdown menu. * @private */ Blockly.FieldDropdown.prototype.dropdownCreate_ = function() { @@ -273,8 +290,11 @@ Blockly.FieldDropdown.prototype.dropdownCreate_ = function() { * @private */ Blockly.FieldDropdown.prototype.dropdownDispose_ = function() { - this.menu_.dispose(); + if (this.menu_) { + this.menu_.dispose(); + } this.menu_ = null; + this.selectedMenuItem_ = null; }; /** @@ -284,15 +304,16 @@ Blockly.FieldDropdown.prototype.dropdownDispose_ = function() { */ Blockly.FieldDropdown.prototype.handleMenuActionEvent_ = function(menuItem) { Blockly.DropDownDiv.hideIfOwner(this, true); - this.onItemSelected(this.menu_, menuItem); + this.onItemSelected_(/** @type {!Blockly.Menu} */ (this.menu_), menuItem); }; /** * Handle the selection of an item in the dropdown menu. * @param {!Blockly.Menu} menu The Menu component clicked. * @param {!Blockly.MenuItem} menuItem The MenuItem selected within menu. + * @protected */ -Blockly.FieldDropdown.prototype.onItemSelected = function(menu, menuItem) { +Blockly.FieldDropdown.prototype.onItemSelected_ = function(menu, menuItem) { this.setValue(menuItem.getValue()); }; @@ -492,7 +513,8 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function(imageJson) { this.imageElement_.setAttribute('height', imageJson.height); this.imageElement_.setAttribute('width', imageJson.width); - var arrowWidth = Blockly.utils.dom.getTextWidth(this.arrow_); + var arrowWidth = Blockly.utils.dom.getTextWidth( + /** @type {!SVGTSpanElement} */ (this.arrow_)); var imageHeight = Number(imageJson.height); var imageWidth = Number(imageJson.width); @@ -531,9 +553,9 @@ Blockly.FieldDropdown.prototype.renderSelectedText_ = function() { }; /** - * Use the `getText_` developer hook to override the field's text representation. - * Get the selected option text. If the selected option is an image - * we return the image alt text. + * Use the `getText_` developer hook to override the field's text + * representation. Get the selected option text. If the selected option is an + * image we return the image alt text. * @return {?string} Selected option text. * @protected * @override @@ -611,4 +633,5 @@ Blockly.FieldDropdown.prototype.onBlocklyAction = function(action) { return Blockly.FieldDropdown.superClass_.onBlocklyAction.call(this, action); }; + Blockly.fieldRegistry.register('field_dropdown', Blockly.FieldDropdown); diff --git a/core/field_image.js b/core/field_image.js index cd7d0df10..fe699a867 100644 --- a/core/field_image.js +++ b/core/field_image.js @@ -115,6 +115,13 @@ Blockly.FieldImage = function(src, width, height, if (typeof opt_onClick == 'function') { this.clickHandler_ = opt_onClick; } + + /** + * The rendered field's image element. + * @type {SVGImageElement} + * @private + */ + this.imageElement_ = null; }; Blockly.utils.object.inherits(Blockly.FieldImage, Blockly.Field); @@ -173,14 +180,15 @@ Blockly.FieldImage.prototype.configure_ = function(config) { * @package */ Blockly.FieldImage.prototype.initView = function() { - this.imageElement_ = Blockly.utils.dom.createSvgElement( - 'image', - { - 'height': this.imageHeight_ + 'px', - 'width': this.size_.width + 'px', - 'alt': this.altText_ - }, - this.fieldGroup_); + this.imageElement_ = /** @type {!SVGImageElement} */ + (Blockly.utils.dom.createSvgElement( + 'image', + { + 'height': this.imageHeight_ + 'px', + 'width': this.size_.width + 'px', + 'alt': this.altText_ + }, + this.fieldGroup_)); this.imageElement_.setAttributeNS(Blockly.utils.dom.XLINK_NS, 'xlink:href', /** @type {string} */ (this.value_)); }; @@ -208,7 +216,7 @@ Blockly.FieldImage.prototype.doValueUpdate_ = function(newValue) { this.value_ = newValue; if (this.imageElement_) { this.imageElement_.setAttributeNS(Blockly.utils.dom.XLINK_NS, - 'xlink:href', this.value_ || ''); + 'xlink:href', String(this.value_)); } }; diff --git a/core/field_multilineinput.js b/core/field_multilineinput.js index 2b1f21c73..349afc37e 100644 --- a/core/field_multilineinput.js +++ b/core/field_multilineinput.js @@ -58,6 +58,13 @@ Blockly.FieldMultilineInput = function(opt_value, opt_validator, opt_config) { } Blockly.FieldMultilineInput.superClass_.constructor.call(this, opt_value, opt_validator, opt_config); + + /** + * The SVG group element that will contain a text element for each text row + * when initialized. + * @type {SVGGElement} + */ + this.textGroup_ = null; }; Blockly.utils.object.inherits(Blockly.FieldMultilineInput, Blockly.FieldTextInput); @@ -89,10 +96,11 @@ Blockly.FieldMultilineInput.fromJson = function(options) { */ Blockly.FieldMultilineInput.prototype.initView = function() { this.createBorderRect_(); - this.textGroup_ = Blockly.utils.dom.createSvgElement('g', - { - 'class': 'blocklyEditableText', - }, this.fieldGroup_); + this.textGroup_ = /** @type {!SVGGElement} **/ + (Blockly.utils.dom.createSvgElement('g', + { + 'class': 'blocklyEditableText', + }, this.fieldGroup_)); }; /** @@ -167,12 +175,13 @@ Blockly.FieldMultilineInput.prototype.render_ = function() { } else { this.resizeEditor_(); } + var htmlInput = /** @type {!HTMLElement} */(this.htmlInput_); if (!this.isTextValid_) { - Blockly.utils.dom.addClass(this.htmlInput_, 'blocklyInvalidInput'); - Blockly.utils.aria.setState(this.htmlInput_, 'invalid', true); + Blockly.utils.dom.addClass(htmlInput, 'blocklyInvalidInput'); + Blockly.utils.aria.setState(htmlInput, 'invalid', true); } else { - Blockly.utils.dom.removeClass(this.htmlInput_, 'blocklyInvalidInput'); - Blockly.utils.aria.setState(this.htmlInput_, 'invalid', false); + Blockly.utils.dom.removeClass(htmlInput, 'blocklyInvalidInput'); + Blockly.utils.aria.setState(htmlInput, 'invalid', false); } } }; @@ -186,7 +195,7 @@ Blockly.FieldMultilineInput.prototype.updateSize_ = function() { var totalWidth = 0; var totalHeight = 0; for (var i = 0; i < nodes.length; i++) { - var tspan = nodes[i]; + var tspan = /** @type {!Element} */ (nodes[i]); var textWidth = Blockly.utils.dom.getTextWidth(tspan); if (textWidth > totalWidth) { totalWidth = textWidth; diff --git a/core/field_textinput.js b/core/field_textinput.js index f9079fc31..f6ebb0a22 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -64,6 +64,26 @@ Blockly.FieldTextInput = function(opt_value, opt_validator, opt_config) { } Blockly.FieldTextInput.superClass_.constructor.call(this, opt_value, opt_validator, opt_config); + + /** + * The HTML input element. + * @type {HTMLElement} + */ + this.htmlInput_ = null; + + /** + * Key down event data. + * @type {?Blockly.EventData} + * @private + */ + this.onKeyDownWrapper_ = null; + + /** + * Key input event data. + * @type {?Blockly.EventData} + * @private + */ + this.onKeyInputWrapper_ = null; }; Blockly.utils.object.inherits(Blockly.FieldTextInput, Blockly.Field); @@ -183,12 +203,13 @@ Blockly.FieldTextInput.prototype.render_ = function() { } else { this.resizeEditor_(); } + var htmlInput = /** @type {!HTMLElement} */(this.htmlInput_); if (!this.isTextValid_) { - Blockly.utils.dom.addClass(this.htmlInput_, 'blocklyInvalidInput'); - Blockly.utils.aria.setState(this.htmlInput_, 'invalid', true); + Blockly.utils.dom.addClass(htmlInput, 'blocklyInvalidInput'); + Blockly.utils.aria.setState(htmlInput, 'invalid', true); } else { - Blockly.utils.dom.removeClass(this.htmlInput_, 'blocklyInvalidInput'); - Blockly.utils.aria.setState(this.htmlInput_, 'invalid', false); + Blockly.utils.dom.removeClass(htmlInput, 'blocklyInvalidInput'); + Blockly.utils.aria.setState(htmlInput, 'invalid', false); } } }; @@ -338,8 +359,12 @@ Blockly.FieldTextInput.prototype.bindInputEvents_ = function(htmlInput) { * @private */ Blockly.FieldTextInput.prototype.unbindInputEvents_ = function() { - Blockly.unbindEvent_(this.onKeyDownWrapper_); - Blockly.unbindEvent_(this.onKeyInputWrapper_); + if (this.onKeyDownWrapper_) { + Blockly.unbindEvent_(this.onKeyDownWrapper_); + } + if (this.onKeyInputWrapper_) { + Blockly.unbindEvent_(this.onKeyInputWrapper_); + } }; /** diff --git a/core/field_variable.js b/core/field_variable.js index 159a9914e..5ac85d736 100644 --- a/core/field_variable.js +++ b/core/field_variable.js @@ -440,8 +440,9 @@ Blockly.FieldVariable.dropdownCreate = function() { * In the rename case, prompt the user for a new name. * @param {!Blockly.Menu} menu The Menu component clicked. * @param {!Blockly.MenuItem} menuItem The MenuItem selected within menu. + * @protected */ -Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) { +Blockly.FieldVariable.prototype.onItemSelected_ = function(menu, menuItem) { var id = menuItem.getValue(); // Handle special cases. if (this.sourceBlock_ && this.sourceBlock_.workspace) {