mirror of
https://github.com/google/blockly.git
synced 2026-01-08 09:30:06 +01:00
Fixed field disposal, and reorganized editor creation.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = '';
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user