Cache dynamic dropdown options (#3036)

* Cache dynamic dropdown options and only re-generate on initialization and render.
This commit is contained in:
Sam El-Husseini
2019-09-18 13:05:25 -07:00
committed by GitHub
parent 7d76cafd61
commit c524980f31
3 changed files with 53 additions and 18 deletions

View File

@@ -45,14 +45,15 @@ goog.require('Blockly.utils.userAgent');
/**
* Class for an editable dropdown field.
* @param {(!Array.<!Array>|!Function)} menuGenerator An array of options
* for a dropdown list, or a function which generates these options.
* @param {(!Array.<!Array>|!Function)} menuGenerator A non-empty array of
* options for a dropdown list, or a function which generates these options.
* @param {Function=} opt_validator A function that is called to validate
* changes to the field's value. Takes in a language-neutral dropdown
* option & returns a validated language-neutral dropdown option, or null to
* abort the change.
* @extends {Blockly.Field}
* @constructor
* @throws {TypeError} If `menuGenerator` options are incorrectly structured.
*/
Blockly.FieldDropdown = function(menuGenerator, opt_validator) {
if (typeof menuGenerator != 'function') {
@@ -68,15 +69,22 @@ Blockly.FieldDropdown = function(menuGenerator, opt_validator) {
this.menuGenerator_ = menuGenerator;
/**
* The currently selected index. A value of -1 indicates no option
* has been selected.
* A cache of the most recently generated options.
* @type {Array.<!Array.<string>>}
* @private
*/
this.generatedOptions_ = null;
/**
* The currently selected index. The field is initialized with the
* first option selected.
* @type {number}
* @private
*/
this.selectedIndex_ = -1;
this.selectedIndex_ = 0;
this.trimOptions_();
var firstTuple = this.getOptions()[0];
var firstTuple = this.getOptions(false)[0];
// Call parent's constructor.
Blockly.FieldDropdown.superClass_.constructor.call(this, firstTuple[1],
@@ -230,7 +238,8 @@ Blockly.FieldDropdown.prototype.widgetCreate_ = function() {
menu.setRightToLeft(this.sourceBlock_.RTL);
menu.setRole('listbox');
var options = this.getOptions();
var options = this.getOptions(false);
this.selectedMenuItem_ = null;
for (var i = 0; i < options.length; i++) {
var content = options[i][0]; // Human-readable text or image.
var value = options[i][1]; // Language-neutral value.
@@ -421,15 +430,19 @@ Blockly.FieldDropdown.prototype.isOptionListDynamic = function() {
/**
* Return a list of the options for this dropdown.
* @return {!Array.<!Array>} Array of option tuples:
* @param {boolean=} opt_useCache For dynamic options, whether or not to use the
* cached options or to re-generate them.
* @return {!Array.<!Array>} A non-empty array of option tuples:
* (human-readable text or image, language-neutral name).
* @throws If generated options are incorrectly structured.
* @throws {TypeError} If generated options are incorrectly structured.
*/
Blockly.FieldDropdown.prototype.getOptions = function() {
Blockly.FieldDropdown.prototype.getOptions = function(opt_useCache) {
if (this.isOptionListDynamic()) {
var generatedOptions = this.menuGenerator_.call(this);
Blockly.FieldDropdown.validateOptions_(generatedOptions);
return generatedOptions;
if (!this.generatedOptions_ || !opt_useCache) {
this.generatedOptions_ = this.menuGenerator_.call(this);
Blockly.FieldDropdown.validateOptions_(this.generatedOptions_);
}
return this.generatedOptions_;
}
return /** @type {!Array.<!Array.<string>>} */ (this.menuGenerator_);
};
@@ -442,7 +455,7 @@ Blockly.FieldDropdown.prototype.getOptions = function() {
*/
Blockly.FieldDropdown.prototype.doClassValidation_ = function(opt_newValue) {
var isValueValid = false;
var options = this.getOptions();
var options = this.getOptions(true);
for (var i = 0, option; option = options[i]; i++) {
// Options are tuples of human-readable text and language-neutral values.
if (option[1] == opt_newValue) {
@@ -468,7 +481,7 @@ Blockly.FieldDropdown.prototype.doClassValidation_ = function(opt_newValue) {
*/
Blockly.FieldDropdown.prototype.doValueUpdate_ = function(newValue) {
Blockly.FieldDropdown.superClass_.doValueUpdate_.call(this, newValue);
var options = this.getOptions();
var options = this.getOptions(true);
for (var i = 0, option; option = options[i]; i++) {
if (option[1] == this.value_) {
this.selectedIndex_ = i;
@@ -501,7 +514,7 @@ Blockly.FieldDropdown.prototype.render_ = function() {
this.imageElement_.style.display = 'none';
// Show correct element.
var options = this.getOptions();
var options = this.getOptions(true);
var selectedOption = this.selectedIndex_ >= 0 &&
options[this.selectedIndex_][0];
if (selectedOption && typeof selectedOption == 'object') {
@@ -576,7 +589,7 @@ Blockly.FieldDropdown.prototype.getText_ = function() {
if (this.selectedIndex_ < 0) {
return null;
}
var options = this.getOptions();
var options = this.getOptions(true);
var selectedOption = options[this.selectedIndex_][0];
if (typeof selectedOption == 'object') {
return selectedOption['alt'];
@@ -587,13 +600,16 @@ Blockly.FieldDropdown.prototype.getText_ = function() {
/**
* Validates the data structure to be processed as an options list.
* @param {?} options The proposed dropdown options.
* @throws If proposed options are incorrectly structured.
* @throws {TypeError} If proposed options are incorrectly structured.
* @private
*/
Blockly.FieldDropdown.validateOptions_ = function(options) {
if (!Array.isArray(options)) {
throw TypeError('FieldDropdown options must be an array.');
}
if (!options.length) {
throw TypeError('FieldDropdown options must not be an empty array.');
}
var foundError = false;
for (var i = 0; i < options.length; ++i) {
var tuple = options[i];

View File

@@ -1210,3 +1210,21 @@ Blockly.TestBlocks.removeDynamicDropdownOption_ = function() {
}
})
};
Blockly.Blocks['test_dropdowns_dynamic_random'] = {
init: function() {
var dropdown = new Blockly.FieldDropdown(this.dynamicOptions);
this.appendDummyInput()
.appendField('dynamic random')
.appendField(dropdown, 'OPTIONS');
},
dynamicOptions: function() {
var random = Math.floor(Math.random() * 10) + 1;
var options = [];
for (var i = 0; i < random; i++) {
options.push([String(i), String(i)]);
}
return options;
}
};

View File

@@ -1468,6 +1468,7 @@ var spaghettiXml = [
<block type="test_dropdowns_dynamic"></block>
<button text="Add option" callbackKey="addDynamicOption"></button>
<button text="Remove option" callbackKey="removeDynamicOption"></button>
<block type="test_dropdowns_dynamic_random"></block>
<label text="Other"></label>
<block type="test_dropdowns_long"></block>
<block type="test_dropdowns_images"></block>