Fixed field disposal, and reorganized editor creation.

This commit is contained in:
Beka Westberg
2019-06-06 08:39:01 -07:00
parent d54fa2e4f9
commit 0122f36450
8 changed files with 258 additions and 289 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
};
/**

View File

@@ -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.

View File

@@ -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;
};
/**

View File

@@ -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.

View File

@@ -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 = '';
};
/**