diff --git a/core/field.js b/core/field.js index a5302527f..f4b95e3da 100644 --- a/core/field.js +++ b/core/field.js @@ -111,7 +111,6 @@ Blockly.Field.cacheWidths_ = null; */ Blockly.Field.cacheReference_ = 0; - /** * Name of field. Unique within each block. * Static labels are usually unnamed. @@ -126,12 +125,12 @@ Blockly.Field.prototype.name = undefined; Blockly.Field.prototype.maxDisplayLength = 50; /** - * Get the current value of the field. - * @return {*} Current value. + * A generic value possessed by the field. + * Should generally be non-null, only null when the field is created. + * @type {*} + * @protected */ -Blockly.Field.prototype.getValue = function() { - return this.value_; -}; +Blockly.Field.prototype.value_ = null; /** * Visible text to display. @@ -329,8 +328,12 @@ Blockly.Field.prototype.toXml = function(fieldElement) { /** * Dispose of all DOM objects belonging to this editable field. + * @package */ Blockly.Field.prototype.dispose = function() { + Blockly.DropDownDiv.hideIfOwner(this); + Blockly.WidgetDiv.hideIfOwner(this); + if (this.mouseDownWrapper_) { Blockly.unbindEvent_(this.mouseDownWrapper_); this.mouseDownWrapper_ = null; @@ -785,12 +788,12 @@ Blockly.Field.prototype.setValue = function(newValue) { }; /** - * A generic value possessed by the field. - * Should generally be non-null, only null when the field is created. - * @type {*} - * @protected + * Get the current value of the field. + * @return {*} Current value. */ -Blockly.Field.prototype.value_ = null; +Blockly.Field.prototype.getValue = function() { + return this.value_; +}; /** * Used to validate a value. Returns input by default. Can be overridden by diff --git a/core/field_angle.js b/core/field_angle.js index 36ca268dc..a3c3574d3 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -130,6 +130,15 @@ Blockly.FieldAngle.prototype.initView = function() { this.textElement_.appendChild(this.symbol_); }; +/** + * Dispose of references to DOM elements and events belonging to this field. + * @package + */ +Blockly.FieldAngle.prototype.dispose = function() { + Blockly.FieldAngle.superClass_.dispose.call(this); + this.symbol_ = null; +}; + /** * Updates the graph when the field rerenders. * @private @@ -140,58 +149,55 @@ Blockly.FieldAngle.prototype.render_ = function() { }; /** - * Clean up this FieldAngle, as well as the inherited FieldTextInput. - * @return {!Function} Closure to call on destruction of the WidgetDiv. - * @private - */ -Blockly.FieldAngle.prototype.dispose_ = function() { - var thisField = this; - return function() { - Blockly.FieldAngle.superClass_.dispose_.call(thisField)(); - thisField.gauge_ = null; - if (thisField.clickWrapper_) { - Blockly.unbindEvent_(thisField.clickWrapper_); - } - if (thisField.moveWrapper1_) { - Blockly.unbindEvent_(thisField.moveWrapper1_); - } - if (thisField.moveWrapper2_) { - Blockly.unbindEvent_(thisField.moveWrapper2_); - } - }; -}; - -/** - * Show the inline free-text editor on top of the text. + * Create and show the angle field's editor. * @private */ Blockly.FieldAngle.prototype.showEditor_ = function() { - var noFocus = - Blockly.utils.userAgent.MOBILE || - Blockly.utils.userAgent.ANDROID || - Blockly.utils.userAgent.IPAD; // Mobile browsers have issues with in-line textareas (focus & keyboards). + var noFocus = + Blockly.userAgent.MOBILE || + Blockly.userAgent.ANDROID || + Blockly.userAgent.IPAD; Blockly.FieldAngle.superClass_.showEditor_.call(this, noFocus); - var div = Blockly.DropDownDiv.getContentDiv(); + var editor = this.dropdownCreate_(); + Blockly.DropDownDiv.getContentDiv().appendChild(editor); - // Build the SVG DOM. - var svg = Blockly.utils.dom.createSvgElement('svg', { - 'xmlns': Blockly.utils.dom.SVG_NS, - 'xmlns:html': Blockly.utils.dom.HTML_NS, - 'xmlns:xlink': Blockly.utils.dom.XLINK_NS, + var border = this.sourceBlock_.getColourBorder(); + border = border.colourBorder == null ? + border.colourLight : border.colourBorder; + Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), border); + + Blockly.DropDownDiv.showPositionedByField( + this, this.dropdownDispose_.bind(this)); + + this.updateGraph_(); +}; + +/** + * Create the angle dropdown editor. + * @return {!Element} The newly created angle picker. + * @private + */ +Blockly.FieldAngle.prototype.dropdownCreate_ = function() { + var svg = Blockly.utils.createSvgElement('svg', { + 'xmlns': 'http://www.w3.org/2000/svg', + 'xmlns:html': 'http://www.w3.org/1999/xhtml', + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', 'version': '1.1', 'height': (Blockly.FieldAngle.HALF * 2) + 'px', 'width': (Blockly.FieldAngle.HALF * 2) + 'px' - }, div); - var circle = Blockly.utils.dom.createSvgElement('circle', { - 'cx': Blockly.FieldAngle.HALF, 'cy': Blockly.FieldAngle.HALF, + }, null); + var circle = Blockly.utils.createSvgElement('circle', { + 'cx': Blockly.FieldAngle.HALF, + 'cy': Blockly.FieldAngle.HALF, 'r': Blockly.FieldAngle.RADIUS, 'class': 'blocklyAngleCircle' }, svg); - this.gauge_ = Blockly.utils.dom.createSvgElement('path', - {'class': 'blocklyAngleGauge'}, svg); - this.line_ = Blockly.utils.dom.createSvgElement('line', { + this.gauge_ = Blockly.utils.createSvgElement('path', { + 'class': 'blocklyAngleGauge' + }, svg); + this.line_ = Blockly.utils.createSvgElement('line', { 'x1': Blockly.FieldAngle.HALF, 'y1': Blockly.FieldAngle.HALF, 'class': 'blocklyAngleLine' @@ -210,33 +216,38 @@ Blockly.FieldAngle.prototype.showEditor_ = function() { }, svg); } - var border = this.sourceBlock_.getColourBorder(); - border = (border.colourBorder == null) ? - border.colourLight : border.colourBorder; - - Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), border); - Blockly.DropDownDiv.showPositionedByField(this); // The angle picker is different from other fields in that it updates on // mousemove even if it's not in the middle of a drag. In future we may // change this behaviour. For now, using bindEvent_ instead of // bindEventWithChecks_ allows it to work without a mousedown/touchstart. this.clickWrapper_ = - Blockly.bindEvent_(svg, 'click', this, this.hide_.bind(this)); + Blockly.bindEvent_(svg, 'click', this, this.hide_); this.moveWrapper1_ = Blockly.bindEvent_(circle, 'mousemove', this, this.onMouseMove); this.moveWrapper2_ = Blockly.bindEvent_(this.gauge_, 'mousemove', this, this.onMouseMove); - this.updateGraph_(); + + return svg; }; /** - * Hide the editor and unbind event listeners. + * Dispose of references to DOM elements and events belonging + * to the angle editor. + * @private + */ +Blockly.FieldAngle.prototype.dropdownDispose_ = function() { + this.gauge_ = null; + this.line_ = null; + Blockly.unbindEvent_(this.clickWrapper_); + Blockly.unbindEvent_(this.moveWrapper1_); + Blockly.unbindEvent_(this.moveWrapper2_); +}; + +/** + * Hide the editor. * @private */ Blockly.FieldAngle.prototype.hide_ = function() { - Blockly.unbindEvent_(this.moveWrapper1_); - Blockly.unbindEvent_(this.moveWrapper2_); - Blockly.unbindEvent_(this.clickWrapper_); Blockly.DropDownDiv.hideIfOwner(this); Blockly.WidgetDiv.hide(); }; @@ -287,7 +298,7 @@ Blockly.FieldAngle.prototype.onMouseMove = function(e) { // Update value. var angleString = String(angle); if (angleString != this.text_) { - Blockly.FieldTextInput.htmlInput_.value = angle; + this.htmlInput_.value = angle; this.setValue(angle); // Always render the input angle. this.text_ = angleString; diff --git a/core/field_colour.js b/core/field_colour.js index fb0641878..894f642f9 100644 --- a/core/field_colour.js +++ b/core/field_colour.js @@ -154,14 +154,6 @@ Blockly.FieldColour.prototype.initView = function() { this.borderRect_.style.fill = this.value_; }; -/** - * Close the colour picker if this input is being deleted. - */ -Blockly.FieldColour.prototype.dispose = function() { - Blockly.WidgetDiv.hideIfOwner(this); - Blockly.FieldColour.superClass_.dispose.call(this); -}; - /** * Ensure that the input value is a valid colour. * @param {string=} newValue The input value. @@ -271,20 +263,18 @@ Blockly.FieldColour.prototype.setColumns = function(columns) { }; /** - * Create a palette under the colour field. + * Create and show the colour field's editor. * @private */ Blockly.FieldColour.prototype.showEditor_ = function() { - var picker = this.createWidget_(); + var picker = this.dropdownCreate_(); Blockly.DropDownDiv.getContentDiv().appendChild(picker); + Blockly.DropDownDiv.setColour( this.DROPDOWN_BACKGROUND_COLOUR, this.DROPDOWN_BORDER_COLOUR); - Blockly.DropDownDiv.showPositionedByField(this); - - // Configure event handler on the table to listen for any event in a cell. - Blockly.FieldColour.onUpWrapper_ = Blockly.bindEvent_(picker, - 'mouseup', this, this.onClick_); + Blockly.DropDownDiv.showPositionedByField( + this, this.dropdownDispose_.bind(this)); }; /** @@ -299,22 +289,17 @@ Blockly.FieldColour.prototype.onClick_ = function(e) { cell = cell.parentNode; } var colour = cell && cell.label; - Blockly.WidgetDiv.hide(); - if (this.sourceBlock_) { - // Call any validation function, and allow it to override. - colour = this.callValidator(colour); - } if (colour !== null) { this.setValue(colour); } }; /** - * Create a colour picker widget. + * Create a colour picker dropdown editor. * @return {!Element} The newly created colour picker. * @private */ -Blockly.FieldColour.prototype.createWidget_ = function() { +Blockly.FieldColour.prototype.dropdownCreate_ = function() { var columns = this.columns_ || Blockly.FieldColour.COLUMNS; var colours = this.colours_ || Blockly.FieldColour.COLOURS; var titles = this.titles_ || Blockly.FieldColour.TITLES; @@ -339,18 +324,20 @@ Blockly.FieldColour.prototype.createWidget_ = function() { div.className = 'blocklyColourSelected'; } } + + // Configure event handler on the table to listen for any event in a cell. + this.onUpWrapper_ = Blockly.bindEvent_(table, 'mouseup', this, this.onClick_); + return table; }; /** - * Hide the colour picker widget. + * Dispose of references to DOM elements and events belonging + * to the colour editor. * @private */ -Blockly.FieldColour.widgetDispose_ = function() { - if (Blockly.FieldColour.onUpWrapper_) { - Blockly.unbindEvent_(Blockly.FieldColour.onUpWrapper_); - } - Blockly.Events.setGroup(false); +Blockly.FieldColour.prototype.dropdownDispose_ = function() { + Blockly.unbindEvent_(this.onUpWrapper_); }; Blockly.Field.register('field_colour', Blockly.FieldColour); diff --git a/core/field_date.js b/core/field_date.js index 0878f78b1..843d53562 100644 --- a/core/field_date.js +++ b/core/field_date.js @@ -99,14 +99,6 @@ Blockly.FieldDate.prototype.DROPDOWN_BORDER_COLOUR = 'silver'; */ Blockly.FieldDate.prototype.DROPDOWN_BACKGROUND_COLOUR = 'white'; -/** - * Close the colour picker if this input is being deleted. - */ -Blockly.FieldDate.prototype.dispose = function() { - Blockly.WidgetDiv.hideIfOwner(this); - Blockly.FieldDate.superClass_.dispose.call(this); -}; - /** * Ensure that the input value is a valid date. * @param {string=} newValue The input value. @@ -195,38 +187,28 @@ Blockly.FieldDate.prototype.updateEditor_ = function() { }; /** - * Create a date picker under the date field. + * Create and show the date field's editor. * @private */ Blockly.FieldDate.prototype.showEditor_ = function() { - this.picker_ = this.createWidget_(); + this.picker_ = this.dropdownCreate_(); this.picker_.render(Blockly.DropDownDiv.getContentDiv()); Blockly.utils.dom.addClass(this.picker_.getElement(), 'blocklyDatePicker'); - Blockly.DropDownDiv.showPositionedByField(this); Blockly.DropDownDiv.setColour( this.DROPDOWN_BACKGROUND_COLOUR, this.DROPDOWN_BORDER_COLOUR); - this.updateEditor_(); - var thisField = this; - Blockly.FieldDate.changeEventKey_ = goog.events.listen(this.picker_, - goog.ui.DatePicker.Events.CHANGE, - function(event) { - var date = event.date ? event.date.toIsoString(true) : ''; - thisField.setValue(date); - }); - Blockly.FieldDate.changeEventKey_ = goog.events.listen(this.picker_, - goog.ui.DatePicker.Events.CHANGE_ACTIVE_MONTH, - function(_e) { - thisField.updateEditor_(); - }); + Blockly.DropDownDiv.showPositionedByField( + this, this.dropdownDispose_.bind(this)); + + this.updateEditor_(); }; /** - * Create a date picker widget and render it inside the widget div. + * Create the date dropdown editor. * @return {!goog.ui.DatePicker} The newly created date picker. * @private */ -Blockly.FieldDate.prototype.createWidget_ = function() { +Blockly.FieldDate.prototype.dropdownCreate_ = function() { // Create the date picker using Closure. Blockly.FieldDate.loadLanguage_(); var picker = new goog.ui.DatePicker(); @@ -235,19 +217,43 @@ Blockly.FieldDate.prototype.createWidget_ = function() { picker.setUseNarrowWeekdayNames(true); picker.setUseSimpleNavigationMenu(true); picker.setDate(goog.date.DateTime.fromIsoString(this.getValue())); + + this.changeEventKey_ = goog.events.listen( + picker, + goog.ui.DatePicker.Events.CHANGE, + this.onDateSelected_, + null, + this); + this.activeMonthEventKey_ = goog.events.listen( + picker, + goog.ui.DatePicker.Events.CHANGE_ACTIVE_MONTH, + this.updateEditor_, + null, + this); + return picker; }; /** - * Hide the date picker. + * Dispose of references to DOM elements and events belonging + * to the date editor. * @private */ -Blockly.FieldDate.widgetDispose_ = function() { - if (Blockly.FieldDate.changeEventKey_) { - goog.events.unlistenByKey(Blockly.FieldDate.changeEventKey_); - } +Blockly.FieldDate.prototype.dropdownDispose_ = function() { this.picker_ = null; - Blockly.Events.setGroup(false); + goog.events.unlistenByKey(this.changeEventKey_); + goog.events.unlistenByKey(this.activeMonthEventKey_); +}; + +/** + * Handle a CHANGE event in the date picker. + * TODO: Not sure what the type for goog event information is. + * @param {!Event} event The CHANGE event. + * @private + */ +Blockly.FieldDate.prototype.onDateSelected_ = function(event) { + var date = event.date ? event.date.toIsoString(true) : ''; + this.setValue(date); }; /** diff --git a/core/field_dropdown.js b/core/field_dropdown.js index be5ccfaad..5b1272f2d 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -154,47 +154,46 @@ Blockly.FieldDropdown.prototype.initView = function() { } }; +/** + * Dispose of references to DOM elements and events belonging to this field. + */ +Blockly.FieldDropdown.prototype.dispose = function() { + Blockly.FieldDropdown.superClass_.dispose.call(this); + this.imageElement_ = null; + this.arrow_ = null; +}; + /** * Create a dropdown menu under the text. * @private */ Blockly.FieldDropdown.prototype.showEditor_ = function() { - Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, null); - var menu = this.createMenu_(); - this.addActionListener_(menu); - this.positionMenu_(menu); + Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, + this.widgetDispose_.bind(this)); + this.menu_ = this.widgetCreate_(); + + this.menu_.render(Blockly.WidgetDiv.DIV); + // Element gets created in render. + Blockly.utils.addClass(this.menu_.getElement(), 'blocklyDropdownMenu'); + + this.positionMenu_(this.menu_); + + // Focusing needs to be handled after the menu is rendered and positioned. + // Otherwise it will cause a page scroll to get the misplaced menu in + // view. See issue #1329. + this.menu_.setAllowAutoFocus(true); + this.menu_.getElement().focus(); }; /** - * Add a listener for mouse and keyboard events in the menu and its items. - * @param {!goog.ui.Menu} menu The menu to add listeners to. + * Create the dropdown editor widget. + * @return {goog.ui.Menu} The newly created dropdown menu. * @private */ -Blockly.FieldDropdown.prototype.addActionListener_ = function(menu) { - var thisField = this; - - function callback(e) { - var menu = this; - var menuItem = e.target; - if (menuItem) { - thisField.onItemSelected(menu, menuItem); - } - Blockly.WidgetDiv.hideIfOwner(thisField); - Blockly.Events.setGroup(false); - } - // Listen for mouse/keyboard events. - goog.events.listen(menu, goog.ui.Component.EventType.ACTION, callback); -}; - -/** - * Create and populate the menu and menu items for this dropdown, based on - * the options list. - * @return {!goog.ui.Menu} The populated dropdown menu. - * @private - */ -Blockly.FieldDropdown.prototype.createMenu_ = function() { +Blockly.FieldDropdown.prototype.widgetCreate_ = function() { var menu = new goog.ui.Menu(); menu.setRightToLeft(this.sourceBlock_.RTL); + var options = this.getOptions(); for (var i = 0; i < options.length; i++) { var content = options[i][0]; // Human-readable text or image. @@ -213,9 +212,33 @@ Blockly.FieldDropdown.prototype.createMenu_ = function() { menu.addChild(menuItem, true); menuItem.setChecked(value == this.value_); } + + this.menuActionEventKey_ = goog.events.listen( + menu, + goog.ui.Component.EventType.ACTION, + this.handleMenuActionEvent_, + false, + this); + return menu; }; +Blockly.FieldDropdown.prototype.widgetDispose_ = function() { + this.menu_ = null; + goog.events.unlistenByKey(this.menuActionEventKey_); +}; + +/** + * Handle an ACTION event in the dropdown menu. + * TODO: Not sure what the type for goog event information is. + * @param {!Event} event The CHANGE event. + * @private + */ +Blockly.FieldDropdown.prototype.handleMenuActionEvent_ = function(event) { + Blockly.WidgetDiv.hideIfOwner(this); + this.onItemSelected(this.menu_, event.target); +}; + /** * Place the menu correctly on the screen, taking into account the dimensions * of the menu and the dimensions of the screen so that it doesn't run off any @@ -224,11 +247,9 @@ Blockly.FieldDropdown.prototype.createMenu_ = function() { * @private */ Blockly.FieldDropdown.prototype.positionMenu_ = function(menu) { - // Record viewport dimensions before adding the dropdown. var viewportBBox = Blockly.utils.getViewportBBox(); var anchorBBox = this.getAnchorDimensions_(); - this.createWidget_(menu); var menuSize = Blockly.utils.uiMenu.getSize(menu); var menuMaxHeightPx = Blockly.FieldDropdown.MAX_MENU_HEIGHT_VH @@ -240,26 +261,8 @@ Blockly.FieldDropdown.prototype.positionMenu_ = function(menu) { if (this.sourceBlock_.RTL) { Blockly.utils.uiMenu.adjustBBoxesForRTL(viewportBBox, anchorBBox, menuSize); } - // Position the menu. Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, menuSize, this.sourceBlock_.RTL); - // Calling menuDom.focus() has to wait until after the menu has been placed - // correctly. Otherwise it will cause a page scroll to get the misplaced menu - // in view. See issue #1329. - menu.getElement().focus(); -}; - -/** - * Create and render the menu widget inside Blockly's widget div. - * @param {!goog.ui.Menu} menu The menu to add to the widget div. - * @private - */ -Blockly.FieldDropdown.prototype.createWidget_ = function(menu) { - var div = Blockly.WidgetDiv.DIV; - menu.render(div); - Blockly.utils.dom.addClass(menu.getElement(), 'blocklyDropdownMenu'); - // Enable autofocus after the initial render to avoid issue #1329. - menu.setAllowAutoFocus(true); }; /** @@ -508,14 +511,6 @@ Blockly.FieldDropdown.prototype.renderSelectedText_ = function() { this.size_.width = Blockly.Field.getCachedWidth(this.textElement_); }; -/** - * Close the dropdown menu if this input is being deleted. - */ -Blockly.FieldDropdown.prototype.dispose = function() { - Blockly.WidgetDiv.hideIfOwner(this); - Blockly.FieldDropdown.superClass_.dispose.call(this); -}; - /** * Validates the data structure to be processed as an options list. * @param {?} options The proposed dropdown options. diff --git a/core/field_image.js b/core/field_image.js index cc4ba0617..7a7bb96ec 100644 --- a/core/field_image.js +++ b/core/field_image.js @@ -133,11 +133,10 @@ Blockly.FieldImage.prototype.initView = function() { * Dispose of all DOM objects belonging to this text. */ Blockly.FieldImage.prototype.dispose = function() { - if (this.fieldGroup_) { - Blockly.utils.dom.removeNode(this.fieldGroup_); - this.fieldGroup_ = null; - } + Blockly.FieldImage.superClass_.dispose.call(this); this.imageElement_ = null; + // TODO: Do we need to dispose of this? + this.clickHandler_ = null; }; /** diff --git a/core/field_label.js b/core/field_label.js index 00cb8a35d..efeeb1e6c 100644 --- a/core/field_label.js +++ b/core/field_label.js @@ -87,16 +87,6 @@ Blockly.FieldLabel.prototype.initView = function() { } }; -/** - * Dispose of all DOM objects belonging to this text. - */ -Blockly.FieldLabel.prototype.dispose = function() { - if (this.textElement_) { - Blockly.utils.dom.removeNode(this.textElement_); - this.textElement_ = null; - } -}; - /** * Ensure that the input value casts to a valid string. * @param {string=} newValue The input value. diff --git a/core/field_textinput.js b/core/field_textinput.js index 027f4f683..87a43b75a 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -107,14 +107,6 @@ Blockly.FieldTextInput.prototype.CURSOR = 'text'; */ Blockly.FieldTextInput.prototype.spellcheck_ = true; -/** - * Close the input widget if this input is being deleted. - */ -Blockly.FieldTextInput.prototype.dispose = function() { - Blockly.WidgetDiv.hideIfOwner(this); - Blockly.FieldTextInput.superClass_.dispose.call(this); -}; - /** * Ensure that the input value casts to a valid string. * @param {string=} newValue The input value. @@ -137,15 +129,12 @@ Blockly.FieldTextInput.prototype.doClassValidation_ = function(newValue) { Blockly.FieldTextInput.prototype.doValueInvalid_ = function() { if (this.isBeingEdited_) { this.isTextValid_ = false; - var htmlInput = Blockly.FieldTextInput.htmlInput_; - if (htmlInput) { - var oldValue = this.value_; - // Revert value when the text becomes invalid. - this.value_ = htmlInput.untypedDefaultValue_; - if (this.sourceBlock_ && Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.BlockChange( - this.sourceBlock_, 'field', this.name, oldValue, this.value_)); - } + var oldValue = this.value_; + // Revert value when the text becomes invalid. + this.value_ = this.htmlInput_.untypedDefaultValue_; + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, oldValue, this.value_)); } } }; @@ -177,12 +166,11 @@ Blockly.FieldTextInput.prototype.render_ = function() { // This logic is done in render_ rather than doValueInvalid_ or // doValueUpdate_ so that the code is more centralized. if (this.isBeingEdited_) { - var htmlInput = Blockly.FieldTextInput.htmlInput_; this.resizeEditor_(); if (!this.isTextValid_) { - Blockly.utils.dom.addClass(htmlInput, 'blocklyInvalidInput'); + Blockly.utils.dom.addClass(this.htmlInput_, 'blocklyInvalidInput'); } else { - Blockly.utils.dom.removeClass(htmlInput, 'blocklyInvalidInput'); + Blockly.utils.dom.removeClass(this.htmlInput_, 'blocklyInvalidInput'); } } }; @@ -233,76 +221,74 @@ Blockly.FieldTextInput.prototype.showPromptEditor_ = function() { * @private */ Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) { - // Start editing. this.isBeingEdited_ = true; + Blockly.WidgetDiv.show( + this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this)); + this.htmlInput_ = this.widgetCreate_(); - // Show the div. - Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_()); + if (!quietInput) { + this.htmlInput_.focus(); + this.htmlInput_.select(); + } + + this.bindInputEvents_(); +}; + +Blockly.FieldTextInput.prototype.widgetCreate_ = function() { var div = Blockly.WidgetDiv.DIV; - // Create the input. var htmlInput = document.createElement('input'); htmlInput.className = 'blocklyHtmlInput'; htmlInput.setAttribute('spellcheck', this.spellcheck_); var fontSize = - (Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt'; + (Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt'; div.style.fontSize = fontSize; htmlInput.style.fontSize = fontSize; div.appendChild(htmlInput); - // Assign the current value to the input & resize. htmlInput.value = htmlInput.defaultValue = this.value_; htmlInput.untypedDefaultValue_ = this.value_; htmlInput.oldValue_ = null; this.resizeEditor_(); - // Give it focus. - if (!quietInput) { - htmlInput.focus(); - htmlInput.select(); - } - - // Bind events. this.bindInputEvents_(htmlInput); - // Save it. - Blockly.FieldTextInput.htmlInput_ = htmlInput; + return htmlInput; }; /** - * Bind handlers for user input on this field and size changes on the workspace. - * @param {!HTMLInputElement} htmlInput The htmlInput created in showEditor, to - * which event handlers will be bound. + * Bind handlers for user input on the text input field's editor.. * @private */ -Blockly.FieldTextInput.prototype.bindInputEvents_ = function(htmlInput) { - // Bind to keydown -- trap Enter without IME and Esc to hide. - htmlInput.onKeyDownWrapper_ = +Blockly.FieldTextInput.prototype.bindInputEvents_ = function() { + // Trap Enter without IME and Esc to hide. + this.onKeyDownWrapper_ = Blockly.bindEventWithChecks_( - htmlInput, 'keydown', this, this.onHtmlInputKeyDown_); - // Bind to keyup -- resize after every keystroke. - htmlInput.onKeyUpWrapper_ = + this.htmlInput_, 'keydown', this, this.onHtmlInputKeyDown_); + // Resize after every keystroke. + this.onKeyUpWrapper_ = Blockly.bindEventWithChecks_( - htmlInput, 'keyup', this, this.onHtmlInputChange_); - // Bind to keyPress -- repeatedly resize when holding down a key. - htmlInput.onKeyPressWrapper_ = + this.htmlInput_, 'keyup', this, this.onHtmlInputChange_); + // Repeatedly resize when holding down a key. + this.onKeyPressWrapper_ = Blockly.bindEventWithChecks_( - htmlInput, 'keypress', this, this.onHtmlInputChange_); - htmlInput.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this); - this.workspace_.addChangeListener(htmlInput.onWorkspaceChangeWrapper_); + this.htmlInput_, 'keypress', this, this.onHtmlInputChange_); + + // TODO: Figure out if this is necessary. + this.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this); + this.workspace_.addChangeListener(this.onWorkspaceChangeWrapper_); }; /** * Unbind handlers for user input and workspace size changes. - * @param {!HTMLInputElement} htmlInput The html for this text input. * @private */ -Blockly.FieldTextInput.prototype.unbindInputEvents_ = function(htmlInput) { - Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_); - Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_); - Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_); +Blockly.FieldTextInput.prototype.unbindInputEvents_ = function() { + Blockly.unbindEvent_(this.onKeyDownWrapper_); + Blockly.unbindEvent_(this.onKeyUpWrapper_); + Blockly.unbindEvent_(this.onKeyPressWrapper_); this.workspace_.removeChangeListener( - htmlInput.onWorkspaceChangeWrapper_); + this.onWorkspaceChangeWrapper_); }; /** @@ -311,13 +297,12 @@ Blockly.FieldTextInput.prototype.unbindInputEvents_ = function(htmlInput) { * @private */ Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) { - var htmlInput = Blockly.FieldTextInput.htmlInput_; var tabKey = 9, enterKey = 13, escKey = 27; if (e.keyCode == enterKey) { Blockly.WidgetDiv.hide(); Blockly.DropDownDiv.hideIfOwner(this); } else if (e.keyCode == escKey) { - htmlInput.value = htmlInput.defaultValue; + this.htmlInput_.value = this.htmlInput_.defaultValue; Blockly.WidgetDiv.hide(); Blockly.DropDownDiv.hideIfOwner(this); } else if (e.keyCode == tabKey) { @@ -334,10 +319,9 @@ Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) { * @private */ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) { - var htmlInput = Blockly.FieldTextInput.htmlInput_; - var text = htmlInput.value; - if (text !== htmlInput.oldValue_) { - htmlInput.oldValue_ = text; + var text = this.htmlInput_.value; + if (text !== this.htmlInput_.oldValue_) { + this.htmlInput_.oldValue_ = text; // TODO(#2169): Once issue is fixed the setGroup functionality could be // moved up to the Field setValue method. This would create a @@ -345,7 +329,7 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) { Blockly.Events.setGroup(true); this.setValue(text); // Always render the input text. - this.text_ = Blockly.FieldTextInput.htmlInput_.value; + this.text_ = this.htmlInput_.value; this.forceRerender(); Blockly.Events.setGroup(false); } @@ -384,44 +368,38 @@ Blockly.FieldTextInput.prototype.resizeEditor_ = function() { /** * Close the editor, save the results, and dispose of the editable * text field's elements. - * @return {!Function} Closure to call on destruction of the WidgetDiv. * @private */ Blockly.FieldTextInput.prototype.widgetDispose_ = function() { - var thisField = this; - return function() { - var htmlInput = Blockly.FieldTextInput.htmlInput_; + // Finalize value. + this.isBeingEdited_ = false; + // No need to call setValue because if the widget is being closed the + // latest input text has already been validated. + if (this.value_ !== this.text_) { + // At the end of an edit the text should be the same as the value. It + // may not be if the input text is different than the validated text. + // We should fix that. + this.text_ = String(this.value_); + this.isTextValid_ = true; + this.forceRerender(); + } + // Otherwise don't rerender. - // Finalize value. - thisField.isBeingEdited_ = false; - // No need to call setValue because if the widget is being closed the - // latest input text has already been validated. - if (thisField.value_ !== thisField.text_) { - // At the end of an edit the text should be the same as the value. It - // may not be if the input text is different than the validated text. - // We should fix that. - thisField.text_ = String(thisField.value_); - thisField.isTextValid_ = true; - thisField.forceRerender(); - } - // Otherwise don't rerender. + // Call onFinishEditing + // TODO: Get rid of this or make it less of a hack. + if (this.onFinishEditing_) { + this.onFinishEditing_(this.value_); + } - // Call onFinishEditing - // TODO: Get rid of this or make it less of a hack. - if (thisField.onFinishEditing_) { - thisField.onFinishEditing_(thisField.value_); - } + // Remove htmlInput. + this.unbindInputEvents_(); + this.htmlInput_ = null; - // Remove htmlInput. - thisField.unbindInputEvents_(htmlInput); - Blockly.FieldTextInput.htmlInput_ = null; - - // Delete style properties. - var style = Blockly.WidgetDiv.DIV.style; - style.width = 'auto'; - style.height = 'auto'; - style.fontSize = ''; - }; + // Delete style properties. + var style = Blockly.WidgetDiv.DIV.style; + style.width = 'auto'; + style.height = 'auto'; + style.fontSize = ''; }; /**