diff --git a/core/field_dropdown.js b/core/field_dropdown.js
index 45ce9bf91..5e5f48e10 100644
--- a/core/field_dropdown.js
+++ b/core/field_dropdown.js
@@ -45,14 +45,15 @@ goog.require('Blockly.utils.userAgent');
/**
* Class for an editable dropdown field.
- * @param {(!Array.|!Function)} menuGenerator An array of options
- * for a dropdown list, or a function which generates these options.
+ * @param {(!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.>}
+ * @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 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.} 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.>} */ (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];
diff --git a/tests/blocks/test_blocks.js b/tests/blocks/test_blocks.js
index f8aa6c050..22a1a32c1 100644
--- a/tests/blocks/test_blocks.js
+++ b/tests/blocks/test_blocks.js
@@ -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;
+ }
+};
diff --git a/tests/playground.html b/tests/playground.html
index acacd898f..28a18c58f 100644
--- a/tests/playground.html
+++ b/tests/playground.html
@@ -1468,6 +1468,7 @@ var spaghettiXml = [
+