Porting Loop blocks to JSON (#919)

* Improved documentation on `Blockly.Extensions.buildTooltipForDropdown`
 * Replaced incorrect uses of `@mixes` JSDoc annotation (on mixin extensions) with `@augments Blockly.Block`.
 * Added Blockly.Extensions.buildTooltipWithFieldValue() extension helper.
This commit is contained in:
Andrew n marshall
2017-02-08 11:36:38 -08:00
committed by GitHub
parent 49c9c024e4
commit a4cfd5cbc8
5 changed files with 379 additions and 235 deletions

View File

@@ -273,9 +273,7 @@ Blockly.defineBlocksWithJsonArray([ // Mutator blocks. Do not extract.
/**
* Tooltip text, keyed by block OP value. Used by logic_compare and
* logic_operation blocks.
*
* Messages are not dereferenced here in order to capture possible language
* changes.
* @see {Blockly.Extensions#buildTooltipForDropdown}
* @package
* @readonly
*/
@@ -297,10 +295,10 @@ Blockly.Extensions.register('logic_op_tooltip',
Blockly.Extensions.buildTooltipForDropdown(
'OP', Blockly.Constants.Logic.TOOLTIPS_BY_OP));
/**
* Mutator methods added to controls_if blocks.
* @mixin
* @augments Blockly.Block
* @package
* @readonly
*/
@@ -460,7 +458,6 @@ Blockly.Constants.Logic.CONTROLS_IF_MUTATOR_MIXIN = {
* "controls_if" extension function. Adds mutator, shape updating methods, and
* dynamic tooltip to "controls_if" blocks.
* @this Blockly.Block
* @mixes Blockly.Constants.Logic.CONTROLS_IF_MUTATOR_MIXIN
* @package
*/
Blockly.Constants.Logic.CONTROLS_IF_EXTENSION = function() {
@@ -517,6 +514,7 @@ Blockly.Constants.Logic.fixLogicCompareRtlOpLabels =
/**
* Adds dynamic type validation for the left and right sides of a logic_compate block.
* @mixin
* @augments Blockly.Block
* @package
* @readonly
*/
@@ -557,7 +555,6 @@ Blockly.Constants.Logic.LOGIC_COMPARE_ONCHANGE_MIXIN = {
* dropdown labels, and adds type left and right side type checking to
* "logic_compare" blocks.
* @this Blockly.Block
* @mixes Blockly.Constants.Logic.LOGIC_COMPARE_ONCHANGE_MIXIN
* @package
* @readonly
*/
@@ -577,6 +574,7 @@ Blockly.Extensions.register('logic_compare',
/**
* Adds type coordination between inputs and output.
* @mixin
* @augments Blockly.Block
* @package
* @readonly
*/

View File

@@ -20,165 +20,252 @@
/**
* @fileoverview Loop blocks for Blockly.
*
* This file is scraped to extract a .json file of block definitions. The array
* passed to defineBlocksWithJsonArray(..) must be strict JSON: double quotes
* only, no outside references, no functions, no trailing commas, etc. The one
* exception is end-of-line comments, which the scraper will remove.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Blocks.loops');
goog.provide('Blockly.Blocks.loops'); // Deprecated
goog.provide('Blockly.Constants.Loops');
goog.require('Blockly.Blocks');
/**
* Common HSV hue for all blocks in this category.
* Should be the same as Blockly.Msg.LOOPS_HUE
* @readonly
*/
Blockly.Blocks.loops.HUE = 120;
Blockly.Constants.Loops.HUE = 120;
/** @deprecated Use Blockly.Constants.Loops.HUE */
Blockly.Blocks.loops.HUE = Blockly.Constants.Loops.HUE;
Blockly.Blocks['controls_repeat_ext'] = {
/**
* Block for repeat n times (external number).
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.CONTROLS_REPEAT_TITLE,
"args0": [
{
"type": "input_value",
"name": "TIMES",
"check": "Number"
}
],
"previousStatement": null,
"nextStatement": null,
"colour": Blockly.Blocks.loops.HUE,
"tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP,
"helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL
});
this.appendStatementInput('DO')
.appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);
}
};
Blockly.Blocks['controls_repeat'] = {
/**
* Block for repeat n times (internal number).
* The 'controls_repeat_ext' block is preferred as it is more flexible.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.CONTROLS_REPEAT_TITLE,
"args0": [
{
"type": "field_number",
"name": "TIMES",
"value": 10,
"min": 0,
"precision": 1
}
],
"previousStatement": null,
"nextStatement": null,
"colour": Blockly.Blocks.loops.HUE,
"tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP,
"helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL
});
this.appendStatementInput('DO')
.appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);
}
};
Blockly.Blocks['controls_whileUntil'] = {
/**
* Block for 'do while/until' loop.
* @this Blockly.Block
*/
init: function() {
var OPERATORS =
[[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE, 'WHILE'],
[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL, 'UNTIL']];
this.setHelpUrl(Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL);
this.setColour(Blockly.Blocks.loops.HUE);
this.appendValueInput('BOOL')
.setCheck('Boolean')
.appendField(new Blockly.FieldDropdown(OPERATORS), 'MODE');
this.appendStatementInput('DO')
.appendField(Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO);
this.setPreviousStatement(true);
this.setNextStatement(true);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
var op = thisBlock.getFieldValue('MODE');
var TOOLTIPS = {
'WHILE': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE,
'UNTIL': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL
};
return TOOLTIPS[op];
});
}
};
Blockly.Blocks['controls_for'] = {
/**
* Block for 'for' loop.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.CONTROLS_FOR_TITLE,
"args0": [
{
"type": "field_variable",
"name": "VAR",
"variable": null
},
{
"type": "input_value",
"name": "FROM",
"check": "Number",
"align": "RIGHT"
},
{
"type": "input_value",
"name": "TO",
"check": "Number",
"align": "RIGHT"
},
{
"type": "input_value",
"name": "BY",
"check": "Number",
"align": "RIGHT"
}
],
"inputsInline": true,
"previousStatement": null,
"nextStatement": null,
"colour": Blockly.Blocks.loops.HUE,
"helpUrl": Blockly.Msg.CONTROLS_FOR_HELPURL
});
this.appendStatementInput('DO')
.appendField(Blockly.Msg.CONTROLS_FOR_INPUT_DO);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
return Blockly.Msg.CONTROLS_FOR_TOOLTIP.replace('%1',
thisBlock.getFieldValue('VAR'));
});
Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT
// Block for repeat n times (external number).
{
"type": "controls_repeat_ext",
"message0": "%{BKY_CONTROLS_REPEAT_TITLE}",
"args0": [{
"type": "input_value",
"name": "TIMES",
"check": "Number"
}],
"message1": "%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",
"args1": [{
"type": "input_statement",
"name": "DO"
}],
"previousStatement": null,
"nextStatement": null,
"colour": "%{BKY_LOOPS_HUE}",
"tooltip": "%{BKY_CONTROLS_REPEAT_TOOLTIP}",
"helpUrl": "%{BKY_CONTROLS_REPEAT_HELPURL}"
},
// Block for repeat n times (internal number).
// The 'controls_repeat_ext' block is preferred as it is more flexible.
{
"type": "controls_repeat",
"message0": "%{BKY_CONTROLS_REPEAT_TITLE}",
"args0": [{
"type": "field_number",
"name": "TIMES",
"value": 10,
"min": 0,
"precision": 1
}],
"message1": "%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",
"args1": [{
"type": "input_statement",
"name": "DO"
}],
"previousStatement": null,
"nextStatement": null,
"colour": "%{BKY_LOOPS_HUE}",
"tooltip": "%{BKY_CONTROLS_REPEAT_TOOLTIP}",
"helpUrl": "%{BKY_CONTROLS_REPEAT_HELPURL}"
},
// Block for 'do while/until' loop.
{
"type": "controls_whileUntil",
"message0": "%1 %2",
"args0": [
{
"type": "field_dropdown",
"name": "MODE",
"options": [
["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_WHILE}", "WHILE"],
["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_UNTIL}", "UNTIL"]
]
},
{
"type": "input_value",
"name": "BOOL",
"check": "Boolean"
}
],
"message1": "%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",
"args1": [{
"type": "input_statement",
"name": "DO"
}],
"previousStatement": null,
"nextStatement": null,
"colour": "%{BKY_LOOPS_HUE}",
"helpUrl": "%{BKY_CONTROLS_WHILEUNTIL_HELPURL}",
"extensions": ["controls_whileUntil_tooltip"]
},
// Block for 'for' loop.
{
"type": "controls_for",
"message0": "%{BKY_CONTROLS_FOR_TITLE}",
"args0": [
{
"type": "field_variable",
"name": "VAR",
"variable": null
},
{
"type": "input_value",
"name": "FROM",
"check": "Number",
"align": "RIGHT"
},
{
"type": "input_value",
"name": "TO",
"check": "Number",
"align": "RIGHT"
},
{
"type": "input_value",
"name": "BY",
"check": "Number",
"align": "RIGHT"
}
],
"message1": "%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",
"args1": [{
"type": "input_statement",
"name": "DO"
}],
"inputsInline": true,
"previousStatement": null,
"nextStatement": null,
"colour": "%{BKY_LOOPS_HUE}",
"helpUrl": "%{BKY_CONTROLS_FOR_HELPURL}",
"extensions": [
"contextMenu_newGetVariableBlock",
"controls_for_tooltip"
]
},
// Block for 'for each' loop.
{
"type": "controls_forEach",
"message0": "%{BKY_CONTROLS_FOREACH_TITLE}",
"args0": [
{
"type": "field_variable",
"name": "VAR",
"variable": null
},
{
"type": "input_value",
"name": "LIST",
"check": "Array"
}
],
"message1": "%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",
"args1": [{
"type": "input_statement",
"name": "DO"
}],
"previousStatement": null,
"nextStatement": null,
"colour": "%{BKY_LOOPS_HUE}",
"helpUrl": "%{BKY_CONTROLS_FOREACH_HELPURL}",
"extensions": [
"contextMenu_newGetVariableBlock",
"controls_forEach_tooltip"
]
},
// Block for flow statements: continue, break.
{
"type": "controls_flow_statements",
"message0": "%1",
"args0": [{
"type": "field_dropdown",
"name": "FLOW",
"options": [
["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK}", "BREAK"],
["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE}", "CONTINUE"]
]
}],
"previousStatement": null,
"colour": "%{BKY_LOOPS_HUE}",
"helpUrl": "%{BKY_CONTROLS_FLOW_STATEMENTS_HELPURL}",
"extensions": [
"controls_flow_tooltip",
"controls_flow_in_loop_check"
]
}
]); // END JSON EXTRACT (Do not delete this comment.)
/**
* Tooltips for the 'controls_whileUntil' block, keyed by MODE value.
* @see {Blockly.Extensions#buildTooltipForDropdown}
* @package
* @readonly
*/
Blockly.Constants.Loops.WHILE_UNTIL_TOOLTIPS = {
'WHILE': '%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}',
'UNTIL': '%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL}'
};
Blockly.Extensions.register('controls_whileUntil_tooltip',
Blockly.Extensions.buildTooltipForDropdown(
'MODE', Blockly.Constants.Loops.WHILE_UNTIL_TOOLTIPS));
/**
* Tooltips for the 'controls_flow_statements' block, keyed by FLOW value.
* @see {Blockly.Extensions#buildTooltipForDropdown}
* @package
* @readonly
*/
Blockly.Constants.Loops.BREAK_CONTINUE_TOOLTIPS = {
'BREAK': '%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}',
'CONTINUE': '%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}'
};
Blockly.Extensions.register('controls_flow_tooltip',
Blockly.Extensions.buildTooltipForDropdown(
'FLOW', Blockly.Constants.Loops.BREAK_CONTINUE_TOOLTIPS));
/**
* Mixin to add a context menu item to create a 'variables_get' block.
* Used by blocks 'controls_for' and 'controls_forEach'.
* @mixin
* @augments Blockly.Block
* @package
* @readonly
*/
Blockly.Constants.Loops.CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = {
/**
* Add menu option to create getter block for loop variable.
* Add context menu option to create getter block for the loop's variable.
* (customContextMenu support limited to web BlockSvg.)
* @param {!Array} options List of menu options to add to.
* @this Blockly.Block
*/
customContextMenu: function(options) {
if (!this.isCollapsed()) {
var varName = this.getFieldValue('VAR');
if (!this.isCollapsed() && varName != null) {
var option = {enabled: true};
var name = this.getFieldValue('VAR');
option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name);
var xmlField = goog.dom.createDom('field', null, name);
option.text =
Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', varName);
var xmlField = goog.dom.createDom('field', null, varName);
xmlField.setAttribute('name', 'VAR');
var xmlBlock = goog.dom.createDom('block', null, xmlField);
xmlBlock.setAttribute('type', 'variables_get');
@@ -188,68 +275,34 @@ Blockly.Blocks['controls_for'] = {
}
};
Blockly.Blocks['controls_forEach'] = {
/**
* Block for 'for each' loop.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.CONTROLS_FOREACH_TITLE,
"args0": [
{
"type": "field_variable",
"name": "VAR",
"variable": null
},
{
"type": "input_value",
"name": "LIST",
"check": "Array"
}
],
"previousStatement": null,
"nextStatement": null,
"colour": Blockly.Blocks.loops.HUE,
"helpUrl": Blockly.Msg.CONTROLS_FOREACH_HELPURL
});
this.appendStatementInput('DO')
.appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_DO);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
return Blockly.Msg.CONTROLS_FOREACH_TOOLTIP.replace('%1',
thisBlock.getFieldValue('VAR'));
});
},
customContextMenu: Blockly.Blocks['controls_for'].customContextMenu
};
Blockly.Extensions.registerMixin('contextMenu_newGetVariableBlock',
Blockly.Constants.Loops.CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN);
Blockly.Blocks['controls_flow_statements'] = {
Blockly.Extensions.register('controls_for_tooltip',
Blockly.Extensions.buildTooltipWithFieldValue(
Blockly.Msg.CONTROLS_FOR_TOOLTIP, 'VAR'));
Blockly.Extensions.register('controls_forEach_tooltip',
Blockly.Extensions.buildTooltipWithFieldValue(
Blockly.Msg.CONTROLS_FOREACH_TOOLTIP, 'VAR'));
/**
* This mixin adds a check to make sure the 'controls_flow_statements' block
* is contained in a loop. Otherwise a warning is added to the block.
* @mixin
* @augments Blockly.Block
* @package
* @readonly
*/
Blockly.Constants.Loops.CONTROL_FLOW_CHECK_IN_LOOP_MIXIN = {
/**
* Block for flow statements: continue, break.
* @this Blockly.Block
* List of block types that are loops and thus do not need warnings.
* To add a new loop type add this to your code:
* Blockly.Blocks['controls_flow_statements'].LOOP_TYPES.push('custom_loop');
*/
init: function() {
var OPERATORS =
[[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK, 'BREAK'],
[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE, 'CONTINUE']];
this.setHelpUrl(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL);
this.setColour(Blockly.Blocks.loops.HUE);
this.appendDummyInput()
.appendField(new Blockly.FieldDropdown(OPERATORS), 'FLOW');
this.setPreviousStatement(true);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
var op = thisBlock.getFieldValue('FLOW');
var TOOLTIPS = {
'BREAK': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK,
'CONTINUE': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE
};
return TOOLTIPS[op];
});
},
LOOP_TYPES: ['controls_repeat', 'controls_repeat_ext', 'controls_forEach',
'controls_for', 'controls_whileUntil'],
/**
* Called whenever anything on the workspace changes.
* Add warning if this flow block is not nested inside a loop.
@@ -281,12 +334,8 @@ Blockly.Blocks['controls_flow_statements'] = {
this.setDisabled(true);
}
}
},
/**
* List of block types that are loops and thus do not need warnings.
* To add a new loop type add this to your code:
* Blockly.Blocks['controls_flow_statements'].LOOP_TYPES.push('custom_loop');
*/
LOOP_TYPES: ['controls_repeat', 'controls_repeat_ext', 'controls_forEach',
'controls_for', 'controls_whileUntil']
}
};
Blockly.Extensions.registerMixin('controls_flow_in_loop_check',
Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN);

View File

@@ -379,9 +379,7 @@ Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT
/**
* Mapping of math block OP value to tooltip message for blocks
* math_arithmetic, math_simple, math_trig, and math_on_lists.
*
* Messages are not dereferenced here in order to capture possible language
* changes.
* @see {Blockly.Extensions#buildTooltipForDropdown}
* @package
* @readonly
*/
@@ -430,6 +428,8 @@ Blockly.Extensions.register('math_op_tooltip',
* Mixin for mutator functions in the 'math_is_divisibleby_mutator'
* extension.
* @mixin
* @augments Blockly.Block
* @package
*/
Blockly.Constants.Math.IS_DIVISIBLEBY_MUTATOR_MIXIN = {
/**
@@ -476,8 +476,7 @@ Blockly.Constants.Math.IS_DIVISIBLEBY_MUTATOR_MIXIN = {
* 'math_is_divisibleby_mutator' extension to the 'math_property' block that
* can update the block shape (add/remove divisor input) based on whether
* property is "divisble by".
* @this {Blockly.Block}
* @mixes Blockly.Constants.Math.IS_DIVISIBLEBY_MUTATOR_MIXIN
* @this Blockly.Block
* @package
*/
Blockly.Constants.Math.IS_DIVISIBLE_MUTATOR_EXTENSION = function() {
@@ -493,7 +492,7 @@ Blockly.Extensions.register('math_is_divisibleby_mutator',
/**
* Update the tooltip of 'math_change' block to reference the variable.
* @this {Blockly.Block}
* @this Blockly.Block
* @package
*/
Blockly.Constants.Math.CHANGE_TOOLTIP_EXTENSION = function() {
@@ -504,12 +503,14 @@ Blockly.Constants.Math.CHANGE_TOOLTIP_EXTENSION = function() {
};
Blockly.Extensions.register('math_change_tooltip',
Blockly.Constants.Math.CHANGE_TOOLTIP_EXTENSION);
Blockly.Extensions.buildTooltipWithFieldValue(
Blockly.Msg.MATH_CHANGE_TOOLTIP, 'VAR'));
/**
* Mixin with mutator methods to support alternate output based if the
* 'math_on_list' block uses the 'MODE' operation.
* @mixin
* @augments Blockly.Block
* @package
* @readonly
*/
@@ -550,8 +551,7 @@ Blockly.Constants.Math.LIST_MODES_MUTATOR_MIXIN = {
/**
* Extension to 'math_on_list' blocks that allows support of
* modes operation (outputs a list of numbers).
* @this {Blockly.Block}
* @mixes Blockly.Constants.Math.LIST_MODES_MUTATOR_MIXIN
* @this Blockly.Block
* @package
*/
Blockly.Constants.Math.LIST_MODES_MUTATOR_EXTENSION = function() {

View File

@@ -92,9 +92,18 @@ Blockly.Extensions.apply = function(name, block) {
};
/**
* Builds an extension function that will map a dropdown value to a tooltip string.
* Tooltip strings will be passed through Blockly.utils.checkMessageReferences(..)
* immediately and Blockly.utils.replaceMessageReferences(..) at display time.
* Builds an extension function that will map a dropdown value to a tooltip
* string.
*
* This method includes multiple checks to ensure tooltips, dropdown options,
* and message references are aligned. This aims to catch errors as early as
* possible, without requiring developers to manually test tooltips under each
* option. After the page is loaded, each tooltip text string will be checked
* for matching message keys in the internationalized string table. Deferring
* this until the page is loaded decouples loading dependencies. Later, upon
* loading the first block of any given type, the extension will validate every
* dropdown option has a matching tooltip in the lookupTable. Errors are
* reported as warnings in the console, and are never fatal.
* @param {string} dropdownName The name of the field whose value is the key
* to the lookup table.
* @param {!Object<string, string>} lookupTable The table of field values to
@@ -112,6 +121,7 @@ Blockly.Extensions.buildTooltipForDropdown = function(dropdownName, lookupTable)
if (document) { // Relies on document.readyState
Blockly.utils.runAfterPageLoad(function() {
for (var key in lookupTable) {
// Will print warnings is reference is missing.
Blockly.utils.checkMessageReferences(lookupTable[key]);
}
});
@@ -122,8 +132,6 @@ Blockly.Extensions.buildTooltipForDropdown = function(dropdownName, lookupTable)
* @this {Blockly.Block}
*/
var extensionFn = function() {
var thisBlock = this;
if (this.type && blockTypesChecked.indexOf(this.type) === -1) {
Blockly.Extensions.checkDropdownOptionsInTable_(
this, dropdownName, lookupTable);
@@ -131,15 +139,15 @@ Blockly.Extensions.buildTooltipForDropdown = function(dropdownName, lookupTable)
}
this.setTooltip(function() {
var value = thisBlock.getFieldValue(dropdownName);
var value = this.getFieldValue(dropdownName);
var tooltip = lookupTable[value];
if (tooltip == null) {
if (blockTypesChecked.indexOf(thisBlock.type) === -1) {
if (blockTypesChecked.indexOf(this.type) === -1) {
// Warn for missing values on generated tooltips
var warning = 'No tooltip mapping for value ' + value +
' of field ' + dropdownName;
if (thisBlock.type != null) {
warning += (' of block type ' + thisBlock.type);
if (this.type != null) {
warning += (' of block type ' + this.type);
}
console.warn(warning + '.');
}
@@ -147,7 +155,7 @@ Blockly.Extensions.buildTooltipForDropdown = function(dropdownName, lookupTable)
tooltip = Blockly.utils.replaceMessageReferences(tooltip);
}
return tooltip;
});
}.bind(this));
};
return extensionFn;
};
@@ -158,6 +166,7 @@ Blockly.Extensions.buildTooltipForDropdown = function(dropdownName, lookupTable)
* @param {!Blockly.Block} block The block containing the dropdown
* @param {string} dropdownName The name of the dropdown
* @param {!Object<string, string>} lookupTable The string lookup table
* @private
*/
Blockly.Extensions.checkDropdownOptionsInTable_ =
function(block, dropdownName, lookupTable) {
@@ -175,6 +184,41 @@ Blockly.Extensions.checkDropdownOptionsInTable_ =
}
};
/**
* Builds an extension function that will install a dynamic tooltip. The
* tooltip message should include the string '%1' and that string will be
* replaced with the value of the named field.
* @param {string} msgTemplate The template form to of the message text, with
* %1 placeholder.
* @param {string} fieldName The field with the replacement value.
* @returns {Function} The extension function.
*/
Blockly.Extensions.buildTooltipWithFieldValue =
function(msgTemplate, fieldName) {
// Check the tooltip string messages for invalid references.
// Wait for load, in case Blockly.Msg is not yet populated.
// runAfterPageLoad() does not run in a Node.js environment due to lack of
// document object, in which case skip the validation.
if (document) { // Relies on document.readyState
Blockly.utils.runAfterPageLoad(function() {
// Will print warnings is reference is missing.
Blockly.utils.checkMessageReferences(msgTemplate);
});
}
/**
* The actual extension.
* @this {Blockly.Block}
*/
var extensionFn = function() {
this.setTooltip(function() {
return Blockly.utils.replaceMessageReferences(msgTemplate)
.replace('%1', this.getFieldValue(fieldName));
}.bind(this));
};
return extensionFn;
};
/**
* Configures the tooltip to mimic the parent block when connected. Otherwise,
* uses the tooltip text at the time this extension is initialized. This takes

View File

@@ -5,10 +5,15 @@
<mutation name="test repeat"></mutation>
<next>
<block type="procedures_callnoreturn">
<mutation name="test while"></mutation>
<mutation name="test repeat_ext"></mutation>
<next>
<block type="procedures_callnoreturn">
<mutation name="test foreach"></mutation>
<mutation name="test while"></mutation>
<next>
<block type="procedures_callnoreturn">
<mutation name="test foreach"></mutation>
</block>
</next>
</block>
</next>
</block>
@@ -236,9 +241,57 @@
</block>
</statement>
</block>
<block type="procedures_defnoreturn" x="397" y="628">
<block type="procedures_defnoreturn" x="397" y="448">
<mutation></mutation>
<field name="NAME">test repeat</field>
<statement name="STACK">
<block type="variables_set" inline="false">
<field name="VAR">count</field>
<value name="VALUE">
<block type="math_number">
<field name="NUM">0</field>
</block>
</value>
<next>
<block type="controls_repeat" inline="true">
<field name="TIMES">10</field>
<statement name="DO">
<block type="math_change" inline="false">
<field name="VAR">count</field>
<value name="DELTA">
<block type="math_number">
<field name="NUM">1</field>
</block>
</value>
</block>
</statement>
<next>
<block type="unittest_assertequals" inline="false">
<value name="MESSAGE">
<block type="text">
<field name="TEXT">repeat 10</field>
</block>
</value>
<value name="ACTUAL">
<block type="variables_get">
<field name="VAR">count</field>
</block>
</value>
<value name="EXPECTED">
<block type="math_number">
<field name="NUM">10</field>
</block>
</value>
</block>
</next>
</block>
</next>
</block>
</statement>
</block>
<block type="procedures_defnoreturn" x="397" y="668">
<mutation></mutation>
<field name="NAME">test repeat_ext</field>
<statement name="STACK">
<block type="variables_set" inline="false">
<field name="VAR">count</field>