diff --git a/core/field_variable.js b/core/field_variable.js index 3fa227c39..e00c6110c 100644 --- a/core/field_variable.js +++ b/core/field_variable.js @@ -47,13 +47,45 @@ goog.require('goog.string'); * @constructor */ Blockly.FieldVariable = function(varname, opt_validator, opt_variableTypes) { - Blockly.FieldVariable.superClass_.constructor.call(this, - Blockly.FieldVariable.dropdownCreate, opt_validator); - this.setValue(varname || ''); + // Don't call the FieldDropdown constructor. It'll try too hard. + this.menuGenerator_ = Blockly.FieldVariable.dropdownCreate; + this.size_ = new goog.math.Size(0, Blockly.BlockSvg.MIN_BLOCK_Y); + this.setValidator(opt_validator); + //this.setValue(varname || ''); + // TODO: Add opt_default_type to match default value. If not set, ''. + this.defaultVariableName = (varname || ''); + this.defaultType_ = ''; this.variableTypes = opt_variableTypes; }; goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown); +Blockly.FieldVariable.getOrCreateVariable = function(workspace, text, type, + id) { + var potentialVariableMap = workspace.isFlyout ? + workspace.targetWorkspace.potentialVariableMap_ : null; + if (id) { + var variable = workspace.getVariableById(id); + if (!variable && potentialVariableMap) { + variable = potentialVariableMap.getVariableById(id); + } + } else { + var variable = workspace.getVariable(text, type); + if (!variable && potentialVariableMap) { + variable = potentialVariableMap.getVariable(text, type); + } + } + // Didn't find the variable. + if (!variable) { + if (potentialVariableMap) { + variable = potentialVariableMap.createVariable(text, type, id); + } else { + variable = workspace.createVariable(text, type, id); + } + } + + return variable; +}; + /** * Install this dropdown on a block. */ @@ -69,20 +101,39 @@ Blockly.FieldVariable.prototype.init = function() { }; Blockly.FieldVariable.prototype.initModel = function() { - 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.workspace_ = this.sourceBlock_.isInFlyout ? + // this.sourceBlock_.workspace.targetWorkspace : + // this.sourceBlock_.workspace; + // // TODO: Describe how the potential variable map is different from the variable + // // map; use getters. + // this.variableMap_ = this.sourceBlock_.isInFlyout ? + // this.workspace_.potentialVariableMap_ : this.workspace_.variableMap_; + // var name = this.defaultValue; + // if (!name) { + // // Variables without names get uniquely named for this workspace. + // name = Blockly.Variables.generateUniqueName(this.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. + + // var variable = this.variableMap_.getVariable(name, this.defaultType_); + // if (!variable) { + // variable = this.variableMap_.createVariable(name, this.defaultType_); + // } + if (this.variable_) { + return; // Initialization already happened. } + this.workspace_ = this.sourceBlock_.workspace; + var variable = Blockly.FieldVariable.getOrCreateVariable( + this.workspace_, this.defaultVariableName, this.defaultType_, null); + this.setValue(variable.getId()); +}; + +Blockly.FieldVariable.dispose = function() { + Blockly.FieldVariable.superClass_.dispose.call(this); + this.workspace_ = null; + this.variableMap_ = null; }; /** @@ -101,14 +152,20 @@ Blockly.FieldVariable.prototype.setSourceBlock = function(block) { * @return {string} Current text. */ Blockly.FieldVariable.prototype.getValue = function() { - return this.getText(); + //return this.getText(); + return this.variable_ ? this.variable_.getId() : ''; +}; + +Blockly.FieldVariable.prototype.getText = function() { + //return this.getText(); + return this.variable_ ? this.variable_.name : ''; }; /** - * Set the variable name. + * Set the variable name. (DEPRECATED) * @param {string} value New text. */ -Blockly.FieldVariable.prototype.setValue = function(value) { +Blockly.FieldVariable.prototype.oldSetValue = function(value) { var newValue = value; var newText = value; @@ -131,6 +188,47 @@ Blockly.FieldVariable.prototype.setValue = function(value) { this.setText(newText); }; +/** + * Set the variable ID. + * @param {string} id New variable ID, which must reference an existing + * variable. + */ +Blockly.FieldVariable.prototype.setValue = function(id) { + var workspace = this.sourceBlock_.workspace; + //var variable = this.variableMap_.getVariableById(id); + var potentialVariableMap = workspace.isFlyout ? + workspace.targetWorkspace.potentialVariableMap_ : null; + var variable = workspace.getVariableById(id); + if (!variable && potentialVariableMap) { + variable = potentialVariableMap.getVariableById(id); + } + if (!variable) { + throw new Error('Variable id doesn\'t point to a real variable! ID was ' + + id); + } + // Type checks! + var type = variable.type; + if (!this.typeIsAllowed_(type)) { + throw new Error('Variable type doesn\'t match this field! Type was ' + + type); + } + this.variable_ = variable; + this.setText(variable.name); +}; + +Blockly.FieldVariable.prototype.typeIsAllowed_ = function(type) { + var typeList = this.getVariableTypes_(); + if (!typeList) { + return true; // If it's null, all types are valid. + } + for (var i = 0; i < typeList.length; i++) { + if (type == typeList[i]) { + return true; + } + } + return false; +}; + /** * Return a list of variable types to include in the dropdown. * @return {!Array.} Array of variable types. @@ -138,6 +236,8 @@ Blockly.FieldVariable.prototype.setValue = function(value) { * @private */ Blockly.FieldVariable.prototype.getVariableTypes_ = function() { + // TODO: Why does this happen every time, instead of once when the workspace + // is set? Do we expect the variable types to change that much? var variableTypes = this.variableTypes; if (variableTypes === null) { // If variableTypes is null, return all variable types. @@ -234,7 +334,7 @@ Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) { return; } else if (id == Blockly.DELETE_VARIABLE_ID) { // Delete variable. - workspace.deleteVariable(this.getText()); + workspace.deleteVariableById(this.variable_.getId()); return; } diff --git a/core/variables.js b/core/variables.js index f2367fd13..4788595eb 100644 --- a/core/variables.js +++ b/core/variables.js @@ -330,12 +330,20 @@ Blockly.Variables.promptName = function(promptText, defaultText, callback) { Blockly.Variables.generateVariableFieldXml_ = function(variableModel) { // The variable name may be user input, so it may contain characters that need // to be escaped to create valid XML. - var element = goog.dom.createDom('field'); - element.setAttribute('name', 'VAR'); - element.setAttribute('variabletype', variableModel.type); - element.setAttribute('id', variableModel.getId()); - element.textContent = variableModel.name; + var typeString = variableModel.type; + if (typeString == '') { + typeString = '\'\''; + } + var text = '' + variableModel.name + ''; + return text; + // var element = goog.dom.createDom('field'); + // element.setAttribute('name', 'VAR'); + // element.setAttribute('variabletype', variableModel.type); + // element.setAttribute('id', variableModel.getId()); + // element.textContent = variableModel.name; - var xmlString = Blockly.Xml.domToText(element); - return xmlString; + // var xmlString = Blockly.Xml.domToText(element); + // return xmlString; }; diff --git a/core/workspace.js b/core/workspace.js index 9196333da..41ed1f915 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -84,6 +84,8 @@ Blockly.Workspace = function(opt_options) { * @private */ this.variableMap_ = new Blockly.VariableMap(this); + + this.potentialVariableMap_ = new Blockly.VariableMap(this); }; /** diff --git a/core/xml.js b/core/xml.js index 343cf8f04..a88de9a6e 100644 --- a/core/xml.js +++ b/core/xml.js @@ -86,6 +86,28 @@ Blockly.Xml.blockToDomWithXY = function(block, opt_noId) { return element; }; +Blockly.Xml.fieldToDomVariable_ = function(field, workspace) { + var potentialVariableMap = workspace.isFlyout ? + workspace.targetWorkspace.potentialVariableMap_ : null; + // Ugh that's not true at all. + var id = field.getValue(); + var variable = workspace.getVariableById(id); + if (!variable && potentialVariableMap) { + variable = potentialVariableMap.getVariableById(id); + } + if (variable) { + var container = goog.dom.createDom('field', null, variable.name); + container.setAttribute('name', field.name); + container.setAttribute('id', variable.getId()); + container.setAttribute('variabletype', variable.type); + return container; + } else { + // something went wrong? + console.log('no variable in fieldtodom'); + return null; + } +}; + /** * Encode a field as XML. * @param {!Blockly.Field} field The field to encode. @@ -96,16 +118,13 @@ Blockly.Xml.blockToDomWithXY = function(block, opt_noId) { */ Blockly.Xml.fieldToDom_ = function(field, workspace) { if (field.name && field.EDITABLE) { - var container = goog.dom.createDom('field', null, field.getValue()); - container.setAttribute('name', field.name); if (field instanceof Blockly.FieldVariable) { - var variable = workspace.getVariable(field.getValue()); - if (variable) { - container.setAttribute('id', variable.getId()); - container.setAttribute('variabletype', variable.type); - } + return Blockly.Xml.fieldToDomVariable_(field, workspace); + } else { + var container = goog.dom.createDom('field', null, field.getValue()); + container.setAttribute('name', field.name); + return container; } - return container; } return null; }; @@ -698,6 +717,31 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { return block; }; +Blockly.Xml.domToFieldVariable_ = function(workspace, xml, text, field) { + // TODO (#1199): When we change setValue and getValue to + // interact with IDs instead of names, update this so that we get + // the variable based on ID instead of textContent. + var type = xml.getAttribute('variabletype') || ''; + if (type == '\'\'') { + type = ''; + } + // TODO: Consider using a different name (var_id?) because this is the + // node's ID. + var id = xml.id; + var variable = Blockly.FieldVariable.getOrCreateVariable(workspace, text, + type, id); + + // This should never happen :) + if (type != null && type !== variable.type) { + throw Error('Serialized variable type with id \'' + + variable.getId() + '\' had type ' + variable.type + ', and ' + + 'does not match variable field that references it: ' + + Blockly.Xml.domToText(xml) + '.'); + } + + field.setValue(variable.getId()); +}; + /** * Decode an XML field tag and set the value of that field on the given block. * @param {!Blockly.Block} block The block that is currently being deserialized. @@ -716,29 +760,10 @@ Blockly.Xml.domToField_ = function(block, fieldName, xml) { var workspace = block.workspace; var text = xml.textContent; if (field instanceof Blockly.FieldVariable) { - // TODO (#1199): When we change setValue and getValue to - // interact with IDs instead of names, update this so that we get - // the variable based on ID instead of textContent. - var type = xml.getAttribute('variabletype') || ''; - // TODO: Consider using a different name (varID?) because this is the - // node's ID. - var id = xml.id; - if (id) { - var variable = workspace.getVariableById(id); - } else { - var variable = workspace.getVariable(text, type); - } - if (!variable) { - variable = workspace.createVariable(text, type, id); - } - if (type != null && type !== variable.type) { - throw Error('Serialized variable type with id \'' + - variable.getId() + '\' had type ' + variable.type + ', and ' + - 'does not match variable field that references it: ' + - Blockly.Xml.domToText(xml) + '.'); - } + Blockly.Xml.domToFieldVariable_(workspace, xml, text, field); + } else { + field.setValue(text); } - field.setValue(text); }; /**