Add variable getter field

This commit is contained in:
Rachel Fenichel 2017-02-22 13:49:39 -08:00
parent 57c3666198
commit c9182b97fa
7 changed files with 210 additions and 11 deletions

View file

@ -21,6 +21,7 @@
'use strict';
goog.provide('Blockly.Blocks.data');
goog.provide('Blockly.Constants.Data');
goog.require('Blockly.Blocks');
goog.require('Blockly.Colours');
@ -62,7 +63,8 @@ Blockly.Blocks['data_variable'] = {
"message0": "%1",
"args0": [
{
"type": "input_value",
"type": "field_variable_getter",
"text": "",
"name": "VARIABLE"
}
],
@ -72,7 +74,8 @@ Blockly.Blocks['data_variable'] = {
"colourTertiary": Blockly.Colours.data.tertiary,
"output": "String",
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
"checkboxInFlyout": true
"checkboxInFlyout": true,
"extensions": ["contextMenu_getVariableBlock"]
});
}
};
@ -191,7 +194,8 @@ Blockly.Blocks['data_listcontents'] = {
"message0": "%1",
"args0": [
{
"type": "field_variable",
"type": "field_variable_getter",
"text": "",
"name": "LIST"
}
],
@ -513,3 +517,56 @@ Blockly.Blocks['data_hidelist'] = {
});
}
};
/**
* Mixin to add a context menu for a data_variable block. It adds one item for
* each variable defined on the workspace.
* @mixin
* @augments Blockly.Block
* @package
* @readonly
*/
Blockly.Constants.Data.CUSTOM_CONTEXT_MENU_GET_VARIABLE_MIXIN = {
/**
* 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 variablesList = this.workspace.variableList;
for (var i = 0; i < variablesList.length; i++) {
var option = {enabled: true};
option.text = variablesList[i];
option.callback =
Blockly.Constants.Data.VARIABLE_OPTION_CALLBACK_FACTORY(this,
option.text);
options.push(option);
}
}
}
};
Blockly.Extensions.registerMixin('contextMenu_getVariableBlock',
Blockly.Constants.Data.CUSTOM_CONTEXT_MENU_GET_VARIABLE_MIXIN);
/**
* Callback factory for dropdown menu options associated with a variable getter
* block. Each variable on the workspace gets its own item in the dropdown
* menu, and clicking on that item changes the text of the field on the source
* block.
* @param {!Blockly.Block} block The block to update.
* @param {string} name The new name to display on the block.
* @return {!function()} A function that updates the block with the new name.
*/
Blockly.Constants.Data.VARIABLE_OPTION_CALLBACK_FACTORY = function(block, name) {
return function() {
var variableField = block.getField('VARIABLE');
if (!variableField) {
console.log("Tried to get a variable field on the wrong type of block.");
}
variableField.setText(name);
};
};

View file

@ -31,6 +31,7 @@ goog.require('Blockly.Colours');
goog.require('Blockly.Comment');
goog.require('Blockly.Connection');
goog.require('Blockly.Extensions');
goog.require('Blockly.FieldVariableGetter');
goog.require('Blockly.Input');
goog.require('Blockly.Mutator');
goog.require('Blockly.Warning');
@ -842,7 +843,8 @@ Blockly.Block.prototype.getVars = function() {
var vars = [];
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
if (field instanceof Blockly.FieldVariable) {
if (field instanceof Blockly.FieldVariable ||
field instanceof Blockly.FieldVariableGetter) {
vars.push(field.getValue());
}
}
@ -859,7 +861,8 @@ Blockly.Block.prototype.getVars = function() {
Blockly.Block.prototype.renameVar = function(oldName, newName) {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
if (field instanceof Blockly.FieldVariable &&
if ((field instanceof Blockly.FieldVariable ||
field instanceof Blockly.FieldVariableGetter) &&
Blockly.Names.equals(oldName, field.getValue())) {
field.setValue(newName);
}
@ -1363,6 +1366,9 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
field = new Blockly.FieldNumber(element['value'],
element['min'], element['max'], element['precision']);
break;
case 'field_variable_getter':
field = Blockly.Block.newFieldVariableGetterFromJson_(element);
break;
case 'field_date':
if (Blockly.FieldDate) {
field = new Blockly.FieldDate(element['date']);
@ -1454,6 +1460,18 @@ Blockly.Block.newFieldVariableFromJson_ = function(options) {
return new Blockly.FieldVariable(varname);
};
/**
* Helper function to construct a FieldVariableGetter from a JSON arg object,
* dereferencing any string table references.
* @param {!Object} options A JSON object with options (variable).
* @returns {!Blockly.FieldImage} The new image.
* @private
*/
Blockly.Block.newFieldVariableGetterFromJson_ = function(options) {
var varname = Blockly.utils.replaceMessageReferences(options['text']);
return new Blockly.FieldVariableGetter(varname, options['name'],
options['class']);
};
/**
* Add a value input, statement input or local variable to this block.

View file

@ -266,7 +266,7 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() {
Blockly.FieldDropdown.prototype.onHide = function() {
this.dropDownOpen_ = false;
// Update colour to look selected.
if (!this.disableColourChange_) {
if (!this.disableColourChange_ && this.sourceBlock_) {
if (this.sourceBlock_.isShadow()) {
this.sourceBlock_.setColour(this.savedPrimary_,
this.sourceBlock_.getColourSecondary(), this.sourceBlock_.getColourTertiary());

View file

@ -0,0 +1,92 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Variable getter field. Appears as a label but has a variable
* picker in the right-click menu.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.FieldVariableGetter');
goog.require('Blockly.Field');
/**
* Class for a variable getter field.
* @param {string} text The initial content of the field.
* @param {string} name Optional CSS class for the field's text.
* @extends {Blockly.FieldLabel}
* @constructor
*
*/
Blockly.FieldVariableGetter = function(text, name) {
Blockly.FieldVariableGetter.superClass_.constructor.call(this, text);
this.name_ = name;
};
goog.inherits(Blockly.FieldVariableGetter, Blockly.Field);
/**
* Editable fields are saved by the XML renderer, non-editable fields are not.
*/
Blockly.FieldVariableGetter.prototype.EDITABLE = true;
/**
* Install this field on a block.
*/
Blockly.FieldVariableGetter.prototype.init = function() {
if (this.fieldGroup_) {
// Field has already been initialized once.
return;
}
Blockly.FieldVariableGetter.superClass_.init.call(this);
if (!this.getValue()) {
// Variables without names get uniquely named for this workspace.
var workspace =
this.sourceBlock_.isInFlyout ?
this.sourceBlock_.workspace.targetWorkspace :
this.sourceBlock_.workspace;
this.setValue(Blockly.Variables.generateUniqueName(workspace));
}
// If the selected variable doesn't exist yet, create it.
// For instance, some blocks in the toolbox have variable dropdowns filled
// in by default.
if (!this.sourceBlock_.isInFlyout) {
this.sourceBlock_.workspace.createVariable(this.getValue());
}
};
/**
* This field is editable, but only through the right-click menu.
* @private
*/
Blockly.FieldVariableGetter.prototype.showEditor_ = function() {
// nop.
};
/**
* Add or remove the UI indicating if this field is editable or not.
* This field is editable, but only through the right-click menu.
* Suppress default editable behaviour.
*/
Blockly.FieldVariableGetter.prototype.updateEditable = function() {
// nop.
};

View file

@ -54,6 +54,9 @@ Blockly.Variables.allUsedVariables = function(root) {
} else {
throw 'Not Block or Workspace: ' + root;
}
var ignorableName = Blockly.Variables.noVariableText();
var variableHash = Object.create(null);
// Iterate through every block and add each variable to the hash.
for (var x = 0; x < blocks.length; x++) {
@ -62,7 +65,7 @@ Blockly.Variables.allUsedVariables = function(root) {
for (var y = 0; y < blockVariables.length; y++) {
var varName = blockVariables[y];
// Variable name may be null if the block is only half-built.
if (varName) {
if (varName && !varName.toLowerCase() == ignorableName) {
variableHash[varName.toLowerCase()] = varName;
}
}
@ -116,14 +119,16 @@ Blockly.Variables.flyoutCategory = function(workspace) {
for (var i = 0; i < variableList.length; i++) {
if (Blockly.Blocks['data_variable']) {
// <block type="data_variable">
// <value name="VARIABLE">
// <shadow type="data_variablemenu"></shadow>
// </value>
// <field name="VARIABLE">variablename</field>
// </block>
var block = goog.dom.createDom('block');
block.setAttribute('type', 'data_variable');
block.setAttribute('gap', 8);
block.appendChild(Blockly.Variables.createVariableDom_(variableList[i]));
var field = goog.dom.createDom('field', null, variableList[i]);
field.setAttribute('name', 'VARIABLE');
block.appendChild(field);
xmlList.push(block);
}
}
@ -276,6 +281,16 @@ Blockly.Variables.createMathNumberDom_ = function() {
return value;
};
/**
* Return the text that should be used in a field_variable or
* field_variable_getter when no variable exists.
* TODO: #572
* @return {string} The text to display.
*/
Blockly.Variables.noVariableText = function() {
return "No variable selected";
};
/**
* Return a new variable name that is not yet being used. This will try to
* generate single letter variable names in the range 'i' to 'z' to start with.

View file

@ -278,6 +278,9 @@ Blockly.Workspace.prototype.renameVariable = function(oldName, newName) {
* @param {string} name The new variable's name.
*/
Blockly.Workspace.prototype.createVariable = function(name) {
if (name.toLowerCase() == Blockly.Variables.noVariableText()) {
return;
}
var index = this.variableIndexOf(name);
if (index == -1) {
this.variableList.push(name);

View file

@ -956,6 +956,20 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) {
block.select();
};
/**
* Rename a variable by updating its name in the variable list.
* TODO: google/blockly:#468
* @param {string} oldName Variable to rename.
* @param {string} newName New variable name.
*/
Blockly.WorkspaceSvg.prototype.renameVariable = function(oldName, newName) {
Blockly.WorkspaceSvg.superClass_.renameVariable.call(this, oldName, newName);
// Refresh the toolbox unless there's a drag in progress.
if (this.toolbox_ && this.toolbox_.flyout_ && !Blockly.Flyout.startFlyout_) {
this.toolbox_.refreshSelection();
}
};
/**
* Create a new variable with the given name. Update the flyout to show the new
* variable immediately.