diff --git a/blocks/logic.js b/blocks/logic.js index 53e1216b0..afbd08339 100644 --- a/blocks/logic.js +++ b/blocks/logic.js @@ -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 */ diff --git a/blocks/loops.js b/blocks/loops.js index 84c6e9d31..f0d2a98b9 100644 --- a/blocks/loops.js +++ b/blocks/loops.js @@ -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); diff --git a/blocks/math.js b/blocks/math.js index 3f250c90d..c8e6855b0 100644 --- a/blocks/math.js +++ b/blocks/math.js @@ -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() { diff --git a/core/extensions.js b/core/extensions.js index 7ed26f3d7..ad9db0dc6 100644 --- a/core/extensions.js +++ b/core/extensions.js @@ -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} 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} 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 diff --git a/tests/generators/loops1.xml b/tests/generators/loops1.xml index 7cdff0786..41dd3b633 100644 --- a/tests/generators/loops1.xml +++ b/tests/generators/loops1.xml @@ -5,10 +5,15 @@ - + - + + + + + + @@ -236,9 +241,57 @@ - + test repeat + + + count + + + 0 + + + + + 10 + + + count + + + 1 + + + + + + + + + repeat 10 + + + + + count + + + + + 10 + + + + + + + + + + + + test repeat_ext count