Refactored field validation.

This commit is contained in:
Beka Westberg
2019-05-02 14:41:31 -07:00
parent a4e0091d4e
commit f16c9c0beb
31 changed files with 1270 additions and 905 deletions

View File

@@ -831,7 +831,6 @@ Blockly.Blocks['lists_split'] = {
updateType_: function(newMode) {
var mode = this.getFieldValue('MODE');
if (mode != newMode) {
this.setFieldValue(newMode, 'MODE');
var inputConnection = this.getInput('INPUT').connection;
inputConnection.setShadowDom(null);
var inputBlock = inputConnection.targetBlock();

View File

@@ -864,11 +864,7 @@ Blockly.Constants.Text.TEXT_CHARAT_EXTENSION = function() {
if (newAt != this.isAt_) {
var block = this.sourceBlock_;
block.updateAt_(newAt);
// This menu has been destroyed and replaced. Update the replacement.
block.setFieldValue(value, 'WHERE');
return null;
}
return undefined;
});
this.updateAt_(true);
// Assign 'this' to a variable for use in the tooltip closure below.

View File

@@ -39,14 +39,15 @@ goog.require('goog.style');
/**
* Abstract class for an editable field.
* @param {string} text The initial content of the field.
* @param {function(string):(string|null|undefined)=} opt_validator An optional
* function that is called to validate user input. See setValidator().
* @param {*} value The initial value of the field.
* @param {Function=} opt_validator A function that is called to validate
* changes to the field's value. Takes in a value & returns a validated
* value, or null to abort the change.
* @constructor
*/
Blockly.Field = function(text, opt_validator) {
Blockly.Field = function(value, opt_validator) {
this.size_ = new goog.math.Size(0, Blockly.BlockSvg.MIN_BLOCK_Y);
this.setValue(text);
this.setValue(value);
this.setValidator(opt_validator);
};
@@ -123,6 +124,14 @@ Blockly.Field.prototype.name = undefined;
*/
Blockly.Field.prototype.maxDisplayLength = 50;
/**
* Get the current value of the field.
* @return {*} Current value.
*/
Blockly.Field.prototype.getValue = function() {
return this.value_;
};
/**
* Visible text to display.
* @type {string}
@@ -233,10 +242,11 @@ Blockly.Field.prototype.initView = function() {
'y': 0,
'height': 16
}, this.fieldGroup_);
/** @type {!Element} */
this.textElement_ = Blockly.utils.createSvgElement('text',
{'class': 'blocklyText', 'y': this.size_.height - 12.5},
this.fieldGroup_);
var textNode = document.createTextNode('');
this.textElement_.appendChild(textNode);
this.updateEditable();
@@ -375,14 +385,15 @@ Blockly.Field.prototype.setVisible = function(visible) {
* Sets a new validation function for editable fields, or clears a previously
* set validator.
*
* The validator function takes in the text form of the users input, and
* optionally returns the accepted field text. Alternatively, if the function
* returns null, the field value change aborts. If the function does not return
* anything (or returns undefined), the input value is accepted as valid. This
* is a shorthand for fields using the validator function call as a field-level
* change event notification.
* The validator function takes in the new field value, and returns
* validated value. The validated value could be the input value, a modified
* version of the input value, or null to abort the change.
*
* @param {?function(string):(string|null|undefined)} handler The validator
* If the function does not return anything (or returns undefined) the new
* value is accepted as valid. This is to allow for fields using the
* validated founction as a field-level change event notification.
*
* @param {Function=} handler The validator
* function or null to clear a previous validator.
*/
Blockly.Field.prototype.setValidator = function(handler) {
@@ -401,6 +412,8 @@ Blockly.Field.prototype.getValidator = function() {
* Validates a change. Does nothing. Subclasses may override this.
* @param {string} text The user's text.
* @return {string} No change needed.
* @deprecated May 2019. Override doClassValidation and other relevant 'do'
* functions instead.
*/
Blockly.Field.prototype.classValidator = function(text) {
return text;
@@ -411,6 +424,7 @@ Blockly.Field.prototype.classValidator = function(text) {
* function for the field's class and its parents.
* @param {string} text Proposed text.
* @return {?string} Revised text, or null if invalid.
* @deprecated May 2019. setValue now contains all relevant logic.
*/
Blockly.Field.prototype.callValidator = function(text) {
var classResult = this.classValidator(text);
@@ -452,15 +466,15 @@ Blockly.Field.prototype.updateColour = function() {
};
/**
* Draws the border with the correct width.
* Saves the computed width in a property.
* Used by getSize() to move/resize any dom elements, and get the new size.
*
* All rendering that has an effect on the size/shape of the block should be
* done here, and should be triggered by getSize().
* @protected
*/
Blockly.Field.prototype.render_ = function() {
// Replace the text.
this.textElement_.textContent = this.getDisplayText_();
this.updateWidth();
this.isDirty_ = false;
};
/**
@@ -547,6 +561,7 @@ Blockly.Field.stopCache = function() {
Blockly.Field.prototype.getSize = function() {
if (this.isDirty_) {
this.render_();
this.isDirty_ = false;
} else if (this.visible_ && this.size_.width == 0) {
// If the field is not visible the width will be 0 as well, one of the
// problems with the old system.
@@ -644,39 +659,106 @@ Blockly.Field.prototype.forceRerender = function() {
};
/**
* By default there is no difference between the human-readable text and
* the language-neutral values. Subclasses (such as dropdown) may define this.
* @return {string} Current value.
*/
Blockly.Field.prototype.getValue = function() {
return this.getText();
};
/**
* By default there is no difference between the human-readable text and
* the language-neutral values. Subclasses (such as dropdown) may define this.
* @param {string} newValue New value.
* Used to change the value of the field. Handles validation and events.
* Subclasses should override doClassValidation_ and doValueUpdate_ rather
* than this method.
* @param {*} newValue New value.
*/
Blockly.Field.prototype.setValue = function(newValue) {
var doLogging = false;
if (newValue === null) {
// No change if null.
doLogging && console.log('null, return');
// Not a valid value to check.
return;
}
// Validate input.
var validated = this.callValidator(newValue);
if (validated !== null) {
newValue = validated;
newValue = this.doClassValidation_(newValue);
if (newValue === null) {
doLogging && console.log('invalid, return');
this.doValueInvalid_();
if (this.isDirty_) {
this.forceRerender();
}
return;
}
var localValidator = this.getValidator();
if (localValidator) {
var validatedValue = localValidator.call(this, newValue);
// Sometimes local validators are used as change listeners (bad!) which
// means they might return undefined accidentally, so we'll just ignore that.
if (validatedValue !== undefined) {
newValue = validatedValue;
}
if (newValue === null) {
doLogging && console.log('invalid, return');
this.doValueInvalid_();
if (this.isDirty_) {
this.forceRerender();
}
return;
}
}
// Check for change.
var oldValue = this.getValue();
if (oldValue == newValue) {
if (oldValue === newValue) {
doLogging && console.log('same, return');
// No change.
return;
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, oldValue, newValue));
}
this.setText(newValue);
this.doValueUpdate_(newValue);
if (this.isDirty_) {
this.forceRerender();
}
doLogging && console.log(this.value_);
};
/**
* A generic value possessed by the field.
* Should generally be non-null, only null when the field is created.
* @type {*}
* @protected
*/
Blockly.Field.prototype.value_ = null;
/**
* Used to validate a value. Returns input by default. Can be overridden by
* subclasses, see FieldDropdown.
* @param {*} newValue The value to be validated.
* @return {*} The validated value, same as input by default.
* @protected
*/
Blockly.Field.prototype.doClassValidation_ = function(newValue) {
// For backwards compatibility.
newValue = this.classValidator(newValue);
return newValue;
};
/**
* Used to update the value of a field. Can be overridden by subclasses to do
* custom storage of values/updating of external things.
* @param {*} newValue The value to be saved.
* @protected
*/
Blockly.Field.prototype.doValueUpdate_ = function(newValue) {
this.value_ = newValue;
this.isDirty_ = true;
// For backwards compatibility.
this.text_ = String(newValue);
};
/**
* Used to notify the field an invalid value was input. Can be overiden by
* subclasses, see FieldTextInput.
* No-op by default.
* @protected
*/
Blockly.Field.prototype.doValueInvalid_ = function() {
// NOP
};
/**

View File

@@ -34,21 +34,19 @@ goog.require('Blockly.utils');
/**
* Class for an editable angle field.
* @param {(string|number)=} opt_value The initial content of the field. The
* value should cast to a number, and if it does not, '0' will be used.
* @param {Function=} opt_validator An optional function that is called
* to validate any constraints on what the user entered. Takes the new
* text as an argument and returns the accepted text or null to abort
* the change.
* @param {string|number=} opt_value The initial value of the field. Should cast
* to a number. Defaults to 0.
* @param {Function=} opt_validator A function that is called to validate
* changes to the field's value. Takes in a number & returns a
* validated number, or null to abort the change.
* @extends {Blockly.FieldTextInput}
* @constructor
*/
Blockly.FieldAngle = function(opt_value, opt_validator) {
// Add degree symbol: '360°' (LTR) or '°360' (RTL)
this.symbol_ = Blockly.utils.createSvgElement('tspan', {}, null);
this.symbol_.appendChild(document.createTextNode('\u00B0'));
opt_value = (opt_value && !isNaN(opt_value)) ? String(opt_value) : '0';
opt_value = this.doClassValidation_(opt_value);
if (opt_value === null) {
opt_value = 0;
}
Blockly.FieldAngle.superClass_.constructor.call(
this, opt_value, opt_validator);
};
@@ -120,18 +118,25 @@ Blockly.FieldAngle.WRAP = 360;
Blockly.FieldAngle.RADIUS = Blockly.FieldAngle.HALF - 1;
/**
* Adds degree symbol and recalculates width.
* Saves the computed width in a property.
* Create the block UI for this field.
* @package
*/
Blockly.FieldAngle.prototype.initView = function() {
Blockly.FieldAngle.superClass_.initView.call(this);
// Add the degree symbol to the left of the number, even in RTL (issue #2380)
this.symbol_ = Blockly.utils.createSvgElement('tspan', {}, null);
this.symbol_.appendChild(document.createTextNode('\u00B0'));
};
/**
* Updates the graph when the field rerenders.
* @private
*/
Blockly.FieldAngle.prototype.render_ = function() {
// Update textElement.
this.textElement_.textContent = this.getDisplayText_();
// Insert degree symbol.
// Degree symbol should be left of number, even in RTL (issue #2380).
this.textElement_.appendChild(this.symbol_);
this.updateWidth();
this.updateGraph_();
};
/**
@@ -208,7 +213,6 @@ Blockly.FieldAngle.prototype.showEditor_ = function() {
}, svg);
}
var border = this.sourceBlock_.getColourBorder();
border = border.colourBorder == null ? border.colourLight : border.colourBorder;
@@ -244,6 +248,7 @@ Blockly.FieldAngle.prototype.hide_ = function() {
* @param {!Event} e Mouse move event.
*/
Blockly.FieldAngle.prototype.onMouseMove = function(e) {
// Calculate angle.
var bBox = this.gauge_.ownerSVGElement.getBoundingClientRect();
var dx = e.clientX - bBox.left - Blockly.FieldAngle.HALF;
var dy = e.clientY - bBox.top - Blockly.FieldAngle.HALF;
@@ -268,24 +273,16 @@ Blockly.FieldAngle.prototype.onMouseMove = function(e) {
angle = Math.round(angle / Blockly.FieldAngle.ROUND) *
Blockly.FieldAngle.ROUND;
}
angle = this.callValidator(angle);
Blockly.FieldTextInput.htmlInput_.value = angle;
this.setValue(angle);
this.validate_();
this.resizeEditor_();
};
/**
* Insert a degree symbol.
* @param {?string} text New text.
*/
Blockly.FieldAngle.prototype.setText = function(text) {
Blockly.FieldAngle.superClass_.setText.call(this, text);
if (!this.textElement_) {
// Not rendered yet.
return;
// Update value.
var angleString = String(angle);
if (angleString != this.text_) {
Blockly.FieldTextInput.htmlInput_.value = angle;
this.setValue(angle);
// Always render the input angle.
this.text_ = angleString;
this.forceRerender();
}
this.updateGraph_();
};
/**
@@ -296,6 +293,7 @@ Blockly.FieldAngle.prototype.updateGraph_ = function() {
if (!this.gauge_) {
return;
}
// Always display the input (i.e. getText) even if it is invalid.
var angleDegrees = Number(this.getText()) + Blockly.FieldAngle.OFFSET;
var angleRadians = Blockly.utils.toRadians(angleDegrees);
var path = ['M ', Blockly.FieldAngle.HALF, ',', Blockly.FieldAngle.HALF];
@@ -326,18 +324,16 @@ Blockly.FieldAngle.prototype.updateGraph_ = function() {
};
/**
* Ensure that only an angle may be entered.
* @param {string} text The user's text.
* @return {?string} A string representing a valid angle, or null if invalid.
* Ensure that the input value is a valid angle.
* @param {string|number=} newValue The input value.
* @return {?number} A valid angle, or null if invalid.
* @protected
*/
Blockly.FieldAngle.prototype.classValidator = function(text) {
if (text === null) {
return null;
}
var n = parseFloat(text || 0);
if (isNaN(n)) {
Blockly.FieldAngle.prototype.doClassValidation_ = function(newValue) {
if (isNaN(newValue)) {
return null;
}
var n = parseFloat(newValue || 0);
n = n % 360;
if (n < 0) {
n += 360;
@@ -345,7 +341,7 @@ Blockly.FieldAngle.prototype.classValidator = function(text) {
if (n > Blockly.FieldAngle.WRAP) {
n -= 360;
}
return String(n);
return n;
};
Blockly.Field.register('field_angle', Blockly.FieldAngle);

View File

@@ -32,19 +32,22 @@ goog.require('Blockly.utils');
/**
* Class for a checkbox field.
* @param {string=} opt_state The initial state of the field ('TRUE' or
* 'FALSE'), defaults to 'FALSE'.
* @param {Function=} opt_validator A function that is executed when a new
* option is selected. Its sole argument is the new checkbox state. If
* it returns a value, this becomes the new checkbox state, unless the
* value is null, in which case the change is aborted.
* @param {string|boolean=} opt_value The initial value of the field. Should
* either be 'TRUE', 'FALSE' or a boolean. Defaults to 'FALSE'.
* @param {Function=} opt_validator A function that is called to validate
* changes to the field's value. Takes in a value ('TRUE' or 'FALSE') &
* returns a validated value ('TRUE' or 'FALSE'), or null to abort the
* change.
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldCheckbox = function(opt_state, opt_validator) {
Blockly.FieldCheckbox.superClass_.constructor.call(this, '', opt_validator);
// Set the initial state.
this.setValue(opt_state);
Blockly.FieldCheckbox = function(opt_value, opt_validator) {
opt_value = this.doClassValidation_(opt_value);
if (opt_value === null) {
opt_value = 'FALSE';
}
Blockly.FieldCheckbox.superClass_.constructor.call(this, opt_value, opt_validator);
this.size_.width = Blockly.FieldCheckbox.WIDTH;
};
goog.inherits(Blockly.FieldCheckbox, Blockly.Field);
@@ -56,9 +59,23 @@ goog.inherits(Blockly.FieldCheckbox, Blockly.Field);
* @nocollapse
*/
Blockly.FieldCheckbox.fromJson = function(options) {
return new Blockly.FieldCheckbox(options['checked'] ? 'TRUE' : 'FALSE');
return new Blockly.FieldCheckbox(options['checked']);
};
/**
* The width of a checkbox field.
* @type {number}
* @const
*/
Blockly.FieldCheckbox.WIDTH = 5;
/**
* Character for the checkmark.
* @type {string}
* @const
*/
Blockly.FieldCheckbox.CHECK_CHAR = '\u2713';
/**
* Serializable fields are saved by the XML renderer, non-serializable fields
* are not. Editable fields should also be serializable.
@@ -67,11 +84,6 @@ Blockly.FieldCheckbox.fromJson = function(options) {
*/
Blockly.FieldCheckbox.prototype.SERIALIZABLE = true;
/**
* Character for the checkmark.
*/
Blockly.FieldCheckbox.CHECK_CHAR = '\u2713';
/**
* Mouse cursor style when over the hotspot that initiates editability.
*/
@@ -91,49 +103,98 @@ Blockly.FieldCheckbox.prototype.initView = function() {
this.fieldGroup_);
var textNode = document.createTextNode(Blockly.FieldCheckbox.CHECK_CHAR);
this.checkElement_.appendChild(textNode);
this.checkElement_.style.display = this.state_ ? 'block' : 'none';
};
this.checkElement_.style.display = this.value_ ? 'block' : 'none';
/**
* Return 'TRUE' if the checkbox is checked, 'FALSE' otherwise.
* @return {string} Current state.
*/
Blockly.FieldCheckbox.prototype.getValue = function() {
return String(this.state_).toUpperCase();
};
/**
* Set the checkbox to be checked if newBool is 'TRUE' or true,
* unchecks otherwise.
* @param {string|boolean} newBool New state.
*/
Blockly.FieldCheckbox.prototype.setValue = function(newBool) {
var newState = (typeof newBool == 'string') ?
(newBool.toUpperCase() == 'TRUE') : !!newBool;
if (this.state_ !== newState) {
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, this.state_, newState));
}
this.state_ = newState;
if (this.checkElement_) {
this.checkElement_.style.display = newState ? 'block' : 'none';
}
if (this.borderRect_) {
this.borderRect_.setAttribute('width',
this.size_.width + Blockly.BlockSvg.SEP_SPACE_X);
}
};
/**
* Toggle the state of the checkbox.
* Checkboxes have a constant width.
* @private
*/
Blockly.FieldCheckbox.prototype.render_ = function() {
this.size_.width = Blockly.FieldCheckbox.WIDTH;
};
/**
* Toggle the state of the checkbox on click.
* @protected
*/
Blockly.FieldCheckbox.prototype.showEditor_ = function() {
var newState = !this.state_;
if (this.sourceBlock_) {
// Call any validation function, and allow it to override.
newState = this.callValidator(newState);
this.setValue(!this.value_);
};
/**
* Ensure that the input value is valid ('TRUE' or 'FALSE').
* @param {string|boolean=} newValue The input value.
* @return {?string} A valid value ('TRUE' or 'FALSE), or null if invalid.
* @protected
*/
Blockly.FieldCheckbox.prototype.doClassValidation_ = function(newValue) {
if (newValue === true || newValue === 'TRUE') {
return 'TRUE';
}
if (newState !== null) {
this.setValue(String(newState).toUpperCase());
if (newValue === false || newValue === 'FALSE') {
return 'FALSE';
}
return null;
};
/**
* Update the value of the field, and update the checkElement.
* @param {string} newValue The new value ('TRUE' or 'FALSE') of the field.
* @protected
*/
Blockly.FieldCheckbox.prototype.doValueUpdate_ = function(newValue) {
this.value_ = this.convertValueToBool_(newValue);
// Update visual.
if (this.checkElement_) {
this.checkElement_.style.display = this.value_ ? 'block' : 'none';
}
};
/**
* Get the value of this field, either 'TRUE' or 'FALSE'.
* @return {string} The value of this field.
*/
Blockly.FieldCheckbox.prototype.getValue = function() {
return this.value_ ? 'TRUE' : 'FALSE';
};
/**
* Get the boolean value of this field.
* @return {string} The boolean value of this field.
*/
Blockly.FieldCheckbox.prototype.getValueBoolean = function() {
return this.value_;
};
/**
* Get the text of this field. Used when the block is collapsed.
* @return {string} Text representing the value of this field
* ('true' or 'false').
*/
Blockly.FieldCheckbox.prototype.getText = function() {
return String(this.convertValueToBool_(this.value_));
};
/**
* Convert a value into a pure boolean.
*
* Converts 'TRUE' to true and 'FALSE' to false correctly, everything else
* is cast to a boolean.
* @param {*} value The value to convert.
* @return {boolean} The converted value.
* @private
*/
Blockly.FieldCheckbox.prototype.convertValueToBool_ = function(value) {
if (typeof value == 'string') {
return value == 'TRUE';
} else {
return !!value;
}
};

View File

@@ -34,21 +34,21 @@ goog.require('goog.math.Size');
/**
* Class for a colour input field.
* @param {string=} opt_colour The initial colour in '#rrggbb' format, defaults
* to the first value in the default colour array.
* @param {Function=} opt_validator A function that is executed when a new
* colour is selected. Its sole argument is the new colour value. Its
* return value becomes the selected colour, unless it is undefined, in
* which case the new colour stands, or it is null, in which case the change
* is aborted.
* @param {string=} opt_value The initial value of the field. Should be in
* '#rrggbb' format. Defaults to the first value in the default colour array.
* @param {Function=} opt_validator A function that is called to validate
* changes to the field's value. Takes in a colour string & returns a
* validated colour string ('#rrggbb' format), or null to abort the change.
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldColour = function(opt_colour, opt_validator) {
opt_colour = opt_colour || Blockly.FieldColour.COLOURS[0];
Blockly.FieldColour.superClass_.constructor
.call(this, opt_colour, opt_validator);
this.setText(Blockly.Field.NBSP + Blockly.Field.NBSP + Blockly.Field.NBSP);
Blockly.FieldColour = function(opt_value, opt_validator) {
opt_value = this.doClassValidation_(opt_value);
if (opt_value === null) {
opt_value = Blockly.FieldColour.COLOURS[0];
}
Blockly.FieldColour.superClass_.constructor.call(
this, opt_value, opt_validator);
};
goog.inherits(Blockly.FieldColour, Blockly.Field);
@@ -63,14 +63,6 @@ Blockly.FieldColour.fromJson = function(options) {
return new Blockly.FieldColour(options['colour']);
};
/**
* Serializable fields are saved by the XML renderer, non-serializable fields
* are not. Editable fields should also be serializable.
* @type {boolean}
* @const
*/
Blockly.FieldColour.prototype.SERIALIZABLE = true;
/**
* Default width of a colour field.
* @type {number}
@@ -87,6 +79,20 @@ Blockly.FieldColour.DEFAULT_WIDTH = 16;
*/
Blockly.FieldColour.DEFAULT_HEIGHT = 12;
/**
* Regex that defines the form of a colour string.
* @type {RegExp}
*/
Blockly.FieldColour.COLOUR_REGEX = new RegExp('#[0-9a-fA-F]{6}');
/**
* Serializable fields are saved by the XML renderer, non-serializable fields
* are not. Editable fields should also be serializable.
* @type {boolean}
* @const
*/
Blockly.FieldColour.prototype.SERIALIZABLE = true;
/**
* Array of colours used by this field. If null, use the global list.
* @type {Array.<string>}
@@ -137,7 +143,7 @@ Blockly.FieldColour.prototype.initView = function() {
this.borderRect_.style['fillOpacity'] = 1;
this.borderRect_.setAttribute('width',
this.size_.width + Blockly.BlockSvg.SEP_SPACE_X);
this.setValue(this.getValue());
this.borderRect_.style.fill = this.value_;
};
/**
@@ -154,42 +160,44 @@ Blockly.FieldColour.prototype.dispose = function() {
};
/**
* Colour fields are fixed with, no need to update.
* Render the colour field.
* @private
*/
Blockly.FieldColour.prototype.updateWidth = function() {
// NOP
Blockly.FieldColour.prototype.render_ = function() {
this.size_.width = Blockly.FieldColour.DEFAULT_WIDTH;
};
/**
* Return the current colour.
* @return {string} Current colour in '#rrggbb' format.
* Ensure that the input value is a valid colour.
* @param {string=} newValue The input value.
* @return {?string} A valid colour, or null if invalid.
* @protected
*/
Blockly.FieldColour.prototype.getValue = function() {
return this.colour_;
};
/**
* Set the colour.
* @param {string} colour The new colour in '#rrggbb' format.
*/
Blockly.FieldColour.prototype.setValue = function(colour) {
if (this.sourceBlock_ && Blockly.Events.isEnabled() &&
this.colour_ != colour) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, this.colour_, colour));
Blockly.FieldColour.prototype.doClassValidation_ = function(newValue) {
if (Blockly.FieldColour.COLOUR_REGEX.test(newValue)) {
return newValue.toLowerCase();
}
this.colour_ = colour;
return null;
};
/**
* Update the value of this colour field, and update the displayed colour.
* @param {string} newValue The new colour in '#rrggbb' format.
* @protected
*/
Blockly.FieldColour.prototype.doValueUpdate_ = function(newValue) {
this.value_ = newValue;
if (this.borderRect_) {
this.borderRect_.style.fill = colour;
this.borderRect_.style.fill = newValue;
}
};
/**
* Get the text from this field. Used when the block is collapsed.
* @return {string} Current text.
* Get the text for this field. Used when the block is collapsed.
* @return {string} Text representing the value of this field.
*/
Blockly.FieldColour.prototype.getText = function() {
var colour = this.colour_;
var colour = this.value_;
// Try to use #rgb format if possible, rather than #rrggbb.
var m = colour.match(/^#(.)\1(.)\2(.)\3$/);
if (m) {

View File

@@ -40,21 +40,20 @@ goog.require('goog.ui.DatePicker');
/**
* Class for a date input field.
* @param {string=} opt_date The initial date, defaults to the current day.
* @param {Function=} opt_validator A function that is executed when a new
* date is selected. Its sole argument is the new date value. Its
* return value becomes the selected date, unless it is undefined, in
* which case the new date stands, or it is null, in which case the change
* is aborted.
* @param {string=} opt_value The initial value of the field. Should be in
* 'YYYY-MM-DD' format. Defaults to the current date.
* @param {Function=} opt_validator A function that is called to validate
* changes to the field's value. Takes in a date string & returns a
* validated date string ('YYYY-MM-DD' format), or null to abort the change.
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldDate = function(opt_date, opt_validator) {
if (!opt_date) {
opt_date = new goog.date.Date().toIsoString(true);
Blockly.FieldDate = function(opt_value, opt_validator) {
opt_value = this.doClassValidation_(opt_value);
if (!opt_value) {
opt_value = new goog.date.Date().toIsoString(true);
}
Blockly.FieldDate.superClass_.constructor.call(this, opt_date, opt_validator);
this.setValue(opt_date);
Blockly.FieldDate.superClass_.constructor.call(this, opt_value, opt_validator);
};
goog.inherits(Blockly.FieldDate, Blockly.Field);
@@ -91,28 +90,21 @@ Blockly.FieldDate.prototype.dispose = function() {
};
/**
* Return the current date.
* @return {string} Current date.
* Ensure that the input value is a valid date.
* @param {string=} newValue The input value.
* @return {?string} A valid date, or null if invalid.
* @protected
*/
Blockly.FieldDate.prototype.getValue = function() {
return this.date_;
};
/**
* Set the date.
* @param {string} date The new date.
*/
Blockly.FieldDate.prototype.setValue = function(date) {
if (this.sourceBlock_) {
var validated = this.callValidator(date);
// If the new date is invalid, validation returns null.
// In this case we still want to display the illegal result.
if (validated !== null) {
date = validated;
}
Blockly.FieldDate.prototype.doClassValidation_ = function(newValue) {
if (!newValue) {
return null;
}
this.date_ = date;
Blockly.Field.prototype.setText.call(this, date);
// Check if the new value is parsable or not.
var date = goog.date.Date.fromIsoString(newValue);
if (!date || date.toIsoString(true) != newValue) {
return null;
}
return newValue;
};
/**
@@ -142,10 +134,6 @@ Blockly.FieldDate.prototype.showEditor_ = function() {
function(event) {
var date = event.date ? event.date.toIsoString(true) : '';
Blockly.WidgetDiv.hide();
if (thisField.sourceBlock_) {
// Call any validation function, and allow it to override.
date = thisField.callValidator(date);
}
thisField.setValue(date);
});
};

View File

@@ -42,11 +42,10 @@ goog.require('goog.ui.MenuItem');
* Class for an editable dropdown field.
* @param {(!Array.<!Array>|!Function)} menuGenerator An array of options
* for a dropdown list, or a function which generates these options.
* @param {Function=} opt_validator A function that is executed when a new
* option is selected, with the newly selected value as its sole argument.
* If it returns a value, that value (which must be one of the options) will
* become selected in place of the newly selected option, unless the return
* value is null, in which case the change is aborted.
* @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
*/
@@ -105,13 +104,6 @@ Blockly.FieldDropdown.ARROW_CHAR =
*/
Blockly.FieldDropdown.prototype.CURSOR = 'default';
/**
* Language-neutral currently selected string or image object.
* @type {string|!Object}
* @protected
*/
Blockly.FieldDropdown.prototype.value_ = '';
/**
* SVG image element if currently selected option is an image, or null.
* @type {SVGElement}
@@ -276,14 +268,7 @@ Blockly.FieldDropdown.prototype.getAnchorDimensions_ = function() {
* @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu.
*/
Blockly.FieldDropdown.prototype.onItemSelected = function(menu, menuItem) {
var value = menuItem.getValue();
if (this.sourceBlock_) {
// Call any validation function, and allow it to override.
value = this.callValidator(value);
}
if (value !== null) {
this.setValue(value);
}
this.setValue(menuItem.getValue());
};
/**
@@ -386,32 +371,43 @@ Blockly.FieldDropdown.prototype.getOptions = function() {
};
/**
* Get the language-neutral value from this dropdown menu.
* @return {string} Current text.
* Ensure that the input value is a valid language-neutral option.
* @param {string=} newValue The input value.
* @return {?string} A valid language-neutral option, or null if invalid.
* @protected
*/
Blockly.FieldDropdown.prototype.getValue = function() {
return this.value_;
Blockly.FieldDropdown.prototype.doClassValidation_ = function(newValue) {
var isValueValid = false;
var options = this.getOptions();
for (var i = 0, option; option = options[i]; i++) {
// Options are tuples of human-readable text and language-neutral values.
if (option[1] == newValue) {
isValueValid = true;
break;
}
}
if (!isValueValid) {
if (this.sourceBlock_) {
console.warn('Cannot set the dropdown\'s value to an unavailable option.' +
' Block type: ' + this.sourceBlock_.type + ', Field name: ' + this.name +
', Value: ' + newValue);
}
return null;
}
return newValue;
};
/**
* Set the language-neutral value for this dropdown menu.
* @param {string} newValue New value to set.
* Update the value of this dropdown field.
* @param {string} newValue The new language-enutral value.
* @protected
*/
Blockly.FieldDropdown.prototype.setValue = function(newValue) {
if (newValue === null || (newValue === this.value_ && this.text_)) {
return; // No change if null and text_ was initialized.
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, this.value_, newValue));
}
this.value_ = newValue;
// Look up and display the human-readable text.
Blockly.FieldDropdown.prototype.doValueUpdate_ = function(newValue) {
Blockly.FieldDropdown.superClass_.doValueUpdate_.call(this, newValue);
var options = this.getOptions();
for (var i = 0; i < options.length; i++) {
// Options are tuples of human-readable text and language-neutral values.
if (options[i][1] == newValue) {
var content = options[i][0];
for (var i = 0, option; option = options[i]; i++) {
if (option[1] == this.value_) {
var content = option[0];
if (typeof content == 'object') {
this.imageJson_ = content;
this.text_ = content.alt;
@@ -419,15 +415,8 @@ Blockly.FieldDropdown.prototype.setValue = function(newValue) {
this.imageJson_ = null;
this.text_ = content;
}
// Always rerender if either the value or the text has changed.
this.forceRerender();
return;
}
}
// Value not found. Add it, maybe it will become valid once set
// (like variable names).
this.text_ = newValue;
this.forceRerender();
};
/**

View File

@@ -34,7 +34,7 @@ goog.require('goog.math.Size');
/**
* Class for an image on a block.
* @param {string=} src The URL of the image, defaults to an empty string.
* @param {string=} src The URL of the image. Defaults to an empty string.
* @param {!(string|number)} width Width of the image.
* @param {!(string|number)} height Height of the image.
* @param {string=} opt_alt Optional alt text for when block is collapsed.
@@ -66,8 +66,8 @@ Blockly.FieldImage = function(src, width, height,
this.flipRtl_ = opt_flipRtl;
this.tooltip_ = '';
this.text_ = opt_alt || '';
this.setValue(src || '');
this.setText(opt_alt || '');
if (typeof opt_onClick == 'function') {
this.clickHandler_ = opt_onClick;
@@ -111,11 +111,12 @@ Blockly.FieldImage.prototype.initView = function() {
'image',
{
'height': this.height_ + 'px',
'width': this.width_ + 'px'
'width': this.width_ + 'px',
'alt': this.text_
},
this.fieldGroup_);
this.setValue(this.src_);
this.setText(this.text_);
this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink',
'xlink:href', this.value_);
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
if (this.tooltip_) {
@@ -166,28 +167,28 @@ Blockly.FieldImage.prototype.setTooltip = function(newTip) {
};
/**
* Get the source URL of this image.
* @return {string} Current text.
* @override
* Ensure that the input value (the source URL) is a string.
* @param {string=} newValue The input value
* @return {?string} A string, or null if invalid.
* @protected
*/
Blockly.FieldImage.prototype.getValue = function() {
return this.src_;
Blockly.FieldImage.prototype.doClassValidation_ = function(newValue) {
if (typeof newValue != 'string') {
return null;
}
return newValue;
};
/**
* Set the source URL of this image.
* @param {?string} src New source.
* @override
* Update the value of this image field, and update the displayed image.
* @param {string} newValue The new image src.
* @protected
*/
Blockly.FieldImage.prototype.setValue = function(src) {
if (src === null) {
// No change if null.
return;
}
this.src_ = src;
Blockly.FieldImage.prototype.doValueUpdate_ = function(newValue) {
this.value_ = newValue;
if (this.imageElement_) {
this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink',
'xlink:href', src || '');
'xlink:href', this.value_ || '');
}
};
@@ -220,22 +221,8 @@ Blockly.FieldImage.prototype.setText = function(alt) {
* @private
*/
Blockly.FieldImage.prototype.render_ = function() {
// NOP
};
/**
* Images are fixed width, no need to render even if forced.
*/
Blockly.FieldImage.prototype.forceRerender = function() {
// NOP
};
/**
* Images are fixed width, no need to update.
* @private
*/
Blockly.FieldImage.prototype.updateWidth = function() {
// NOP
this.size_.width = this.width_;
this.size_.height = this.height_ + 2 * Blockly.BlockSvg.INLINE_PADDING_Y;
};
/**

View File

@@ -36,19 +36,20 @@ goog.require('goog.math.Size');
/**
* Class for a non-editable, non-serializable text field.
* @param {string=} text The initial content of the field, defaults to an
* empty string.
* @param {string=} opt_value The initial value of the field. Should cast to a
* string. Defaults to an empty string if null or undefined.
* @param {string=} opt_class Optional CSS class for the field's text.
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldLabel = function(text, opt_class) {
Blockly.FieldLabel = function(opt_value, opt_class) {
this.size_ = new goog.math.Size(0, 17.5);
this.class_ = opt_class;
if (text === null || text === undefined) {
text = '';
opt_value = this.doClassValidation_(opt_value);
if (opt_value === null) {
opt_value = '';
}
this.setValue(String(text));
this.setValue(opt_value);
this.tooltip_ = '';
};
goog.inherits(Blockly.FieldLabel, Blockly.Field);
@@ -84,6 +85,8 @@ Blockly.FieldLabel.prototype.initView = function() {
'class': 'blocklyText',
'y': this.size_.height - 5
}, this.fieldGroup_);
var textNode = document.createTextNode('');
this.textElement_.appendChild(textNode);
if (this.class_) {
Blockly.utils.addClass(this.textElement_, this.class_);
}
@@ -107,6 +110,19 @@ Blockly.FieldLabel.prototype.dispose = function() {
}
};
/**
* Ensure that the input value casts to a valid string.
* @param {string=} newValue The input value.
* @return {?string} A valid string, or null if invalid.
* @protected
*/
Blockly.FieldLabel.prototype.doClassValidation_ = function(newValue) {
if (newValue === null || newValue === undefined) {
return null;
}
return String(newValue);
};
/**
* Change the tooltip text for this field.
* @param {string|!Element} newTip Text for tooltip or a parent element to

View File

@@ -33,14 +33,15 @@ goog.require('Blockly.utils');
/**
* Class for a non-editable, serializable text field.
* @param {!string} text The initial content of the field.
* @param {*} opt_value The initial value of the field. Should cast to a
* string. Defaults to an empty string if null or undefined.
* @param {string=} opt_class Optional CSS class for the field's text.
* @extends {Blockly.FieldLabel}
* @constructor
*
*/
Blockly.FieldLabelSerializable = function(text, opt_class) {
Blockly.FieldLabelSerializable.superClass_.constructor.call(this, text,
Blockly.FieldLabelSerializable = function(opt_value, opt_class) {
Blockly.FieldLabelSerializable.superClass_.constructor.call(this, opt_value,
opt_class);
};
goog.inherits(Blockly.FieldLabelSerializable, Blockly.FieldLabel);

View File

@@ -31,22 +31,23 @@ goog.require('Blockly.FieldTextInput');
/**
* Class for an editable number field.
* @param {(string|number)=} opt_value The initial content of the field.
* The value should cast to a number, and if it does not, '0' will be used.
* @param {string|number=} opt_value The initial value of the field. Should cast
* to a number. Defaults to 0.
* @param {(string|number)=} opt_min Minimum value.
* @param {(string|number)=} opt_max Maximum value.
* @param {(string|number)=} opt_precision Precision for value.
* @param {Function=} opt_validator An optional function that is called
* to validate any constraints on what the user entered. Takes the new
* text as an argument and returns either the accepted text, a replacement
* text, or null to abort the change.
* @extends {Blockly.FieldTextInput}
* @param {Function=} opt_validator A function that is called to validate
* changes to the field's value. Takes in a number & returns a validated
* number, or null to abort the change.
* @constructor
*/
Blockly.FieldNumber = function(opt_value, opt_min, opt_max, opt_precision,
opt_validator) {
this.setConstraints(opt_min, opt_max, opt_precision);
opt_value = (opt_value && !isNaN(opt_value)) ? String(opt_value) : '0';
opt_value = this.doClassValidation_(opt_value);
if (opt_value === null) {
opt_value = 0;
}
Blockly.FieldNumber.superClass_.constructor.call(
this, opt_value, opt_validator);
};
@@ -98,21 +99,26 @@ Blockly.FieldNumber.prototype.setConstraints = function(min, max, precision) {
};
/**
* Ensure that only a number in the correct range may be entered.
* @param {string} text The user's text.
* @return {?string} A string representing a valid number, or null if invalid.
* Ensure that the input value is a valid number (must fulfill the
* constraints placed on the field).
* @param {string|number=} newValue The input value.
* @return {?number} A valid number, or null if invalid.
* @protected
*/
Blockly.FieldNumber.prototype.classValidator = function(text) {
if (text === null) {
Blockly.FieldNumber.prototype.doClassValidation_ = function(newValue) {
if (newValue === null || newValue === undefined) {
return null;
}
text = String(text);
// Clean up text.
newValue = String(newValue);
// TODO: Handle cases like 'ten', '1.203,14', etc.
// 'O' is sometimes mistaken for '0' by inexperienced users.
text = text.replace(/O/ig, '0');
newValue = newValue.replace(/O/ig, '0');
// Strip out thousands separators.
text = text.replace(/,/g, '');
var n = parseFloat(text || 0);
newValue = newValue.replace(/,/g, '');
// Clean up number.
var n = parseFloat(newValue || 0);
if (isNaN(n)) {
// Invalid number.
return null;
@@ -123,8 +129,10 @@ Blockly.FieldNumber.prototype.classValidator = function(text) {
if (this.precision_ && isFinite(n)) {
n = Math.round(n / this.precision_) * this.precision_;
}
return (this.fractionalDigits_ == -1) ? String(n) :
n.toFixed(this.fractionalDigits_);
// Clean up floating point errors.
n = (this.fractionalDigits_ == -1) ? n :
Number(n.toFixed(this.fractionalDigits_));
return n;
};
Blockly.Field.register('field_number', Blockly.FieldNumber);

View File

@@ -37,20 +37,20 @@ goog.require('goog.math.Coordinate');
/**
* Class for an editable text field.
* @param {string=} text The initial content of the field, defaults to an
* empty string.
* @param {Function=} opt_validator An optional function that is called
* to validate any constraints on what the user entered. Takes the new
* text as an argument and returns either the accepted text, a replacement
* text, or null to abort the change.
* @param {string=} opt_value The initial value of the field. Should cast to a
* string. Defaults to an empty string if null or undefined.
* @param {Function=} opt_validator A function that is called to validate
* changes to the field's value. Takes in a string & returns a validated
* string, or null to abort the change.
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldTextInput = function(text, opt_validator) {
if (text === null || text === undefined) {
text = '';
Blockly.FieldTextInput = function(opt_value, opt_validator) {
opt_value = this.doClassValidation_(opt_value);
if (opt_value === null) {
opt_value = '';
}
Blockly.FieldTextInput.superClass_.constructor.call(this, String(text),
Blockly.FieldTextInput.superClass_.constructor.call(this, opt_value,
opt_validator);
};
goog.inherits(Blockly.FieldTextInput, Blockly.Field);
@@ -114,43 +114,75 @@ Blockly.FieldTextInput.prototype.dispose = function() {
};
/**
* Set the value of this field.
* @param {?string} newValue New value.
* @override
* Ensure that the input value casts to a valid string.
* @param {string=} newValue The input value.
* @return {?string} A valid string, or null if invalid.
* @protected
*/
Blockly.FieldTextInput.prototype.setValue = function(newValue) {
if (newValue !== null) { // No change if null.
if (this.sourceBlock_) {
var validated = this.callValidator(newValue);
// If the new value is invalid, validation returns null.
// In this case we still want to display the illegal result.
if (validated !== null) {
newValue = validated;
Blockly.FieldTextInput.prototype.doClassValidation_ = function(newValue) {
if (newValue === null || newValue === undefined) {
return null;
}
return String(newValue);
};
/**
* Called by setValue if the text input is not valid. If the field is
* currently being edited it reverts value of the field to the previous
* value while allowing the display text to be handled by the htmlInput_.
* @protected
*/
Blockly.FieldTextInput.prototype.doValueInvalid_ = function() {
if (this.isBeingEdited_) {
this.isTextValid_ = false;
var htmlInput = Blockly.FieldTextInput.htmlInput_;
if (htmlInput) {
var oldValue = this.value_;
// Revert value when the text becomes invalid.
this.value_ = htmlInput.untypedDefaultValue_;
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, oldValue, this.value_));
}
}
Blockly.Field.prototype.setValue.call(this, newValue);
}
};
/**
* Set the text in this field and fire a change event.
* @param {*} newText New text.
* Called by setValue if the text input is valid. Updates the value of the
* field, and updates the text of the field if it is not currently being
* edited (i.e. handled by the htmlInput_).
* @param {!string} newValue The new validated value of the field.
* @protected
*/
Blockly.FieldTextInput.prototype.setText = function(newText) {
if (newText === null) {
// No change if null.
return;
Blockly.FieldTextInput.prototype.doValueUpdate_ = function(newValue) {
this.isTextValid_ = true;
this.value_ = newValue;
if (!this.isBeingEdited_) {
// This should only occur if setValue is triggered programmatically.
this.text_ = String(newValue);
this.isDirty_ = true;
}
newText = String(newText);
if (newText === this.text_) {
// No change.
return;
};
/**
* Updates the colour of the htmlInput given the current validity of the
* field's value.
* @protected
*/
Blockly.FieldTextInput.prototype.render_ = function() {
Blockly.FieldTextInput.superClass_.render_.call(this);
// This logic is done in render_ rather than doValueInvalid_ or
// doValueUpdate_ so that the code is more centralized.
if (this.isBeingEdited_) {
var htmlInput = Blockly.FieldTextInput.htmlInput_;
this.resizeEditor_();
if (!this.isTextValid_) {
Blockly.utils.addClass(htmlInput, 'blocklyInvalidInput');
} else {
Blockly.utils.removeClass(htmlInput, 'blocklyInvalidInput');
}
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, this.text_, newText));
}
Blockly.Field.prototype.setText.call(this, newText);
};
/**
@@ -188,9 +220,6 @@ Blockly.FieldTextInput.prototype.showPromptEditor_ = function() {
var fieldText = this;
Blockly.prompt(Blockly.Msg['CHANGE_VALUE_TITLE'], this.text_,
function(newValue) {
if (fieldText.sourceBlock_) {
newValue = fieldText.callValidator(newValue);
}
fieldText.setValue(newValue);
});
};
@@ -202,8 +231,13 @@ Blockly.FieldTextInput.prototype.showPromptEditor_ = function() {
* @private
*/
Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
// Start editing.
this.isBeingEdited_ = true;
// Show the div.
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_());
var div = Blockly.WidgetDiv.DIV;
// Create the input.
var htmlInput = document.createElement('input');
htmlInput.className = 'blocklyHtmlInput';
@@ -212,20 +246,25 @@ Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
(Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt';
div.style.fontSize = fontSize;
htmlInput.style.fontSize = fontSize;
Blockly.FieldTextInput.htmlInput_ = htmlInput;
div.appendChild(htmlInput);
htmlInput.value = htmlInput.defaultValue = this.text_;
// Assign the current value to the input & resize.
htmlInput.value = htmlInput.defaultValue = this.value_;
htmlInput.untypedDefaultValue_ = this.value_;
htmlInput.oldValue_ = null;
this.validate_();
this.resizeEditor_();
// Give it focus.
if (!quietInput) {
htmlInput.focus();
htmlInput.select();
}
// Bind events.
this.bindEvents_(htmlInput);
// Save it.
Blockly.FieldTextInput.htmlInput_ = htmlInput;
};
/**
@@ -239,7 +278,7 @@ Blockly.FieldTextInput.prototype.bindEvents_ = function(htmlInput) {
htmlInput.onKeyDownWrapper_ =
Blockly.bindEventWithChecks_(
htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
// Bind to keyup -- trap Enter; resize after every keystroke.
// Bind to keyup -- resize after every keystroke.
htmlInput.onKeyUpWrapper_ =
Blockly.bindEventWithChecks_(
htmlInput, 'keyup', this, this.onHtmlInputChange_);
@@ -294,7 +333,6 @@ Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) {
*/
Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) {
var htmlInput = Blockly.FieldTextInput.htmlInput_;
// Update source block.
var text = htmlInput.value;
if (text !== htmlInput.oldValue_) {
htmlInput.oldValue_ = text;
@@ -304,40 +342,15 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) {
// broader fix for all field types.
Blockly.Events.setGroup(true);
this.setValue(text);
// Always render the input text.
this.text_ = Blockly.FieldTextInput.htmlInput_.value;
this.forceRerender();
Blockly.Events.setGroup(false);
this.validate_();
} else if (Blockly.userAgent.WEBKIT) {
// Cursor key. Render the source block to show the caret moving.
// Chrome only (version 26, OS X).
this.sourceBlock_.render();
}
this.resizeEditor_();
Blockly.svgResize(this.sourceBlock_.workspace);
};
/**
* Check to see if the contents of the editor validates.
* Style the editor accordingly.
* @protected
*/
Blockly.FieldTextInput.prototype.validate_ = function() {
var valid = true;
if (!Blockly.FieldTextInput.htmlInput_) {
throw Error('htmlInput not defined');
}
var htmlInput = Blockly.FieldTextInput.htmlInput_;
if (this.sourceBlock_) {
valid = this.callValidator(htmlInput.value);
}
if (valid === null) {
Blockly.utils.addClass(htmlInput, 'blocklyInvalidInput');
} else {
Blockly.utils.removeClass(htmlInput, 'blocklyInvalidInput');
}
};
/**
* Resize the editor and the underlying block to fit the text.
* Resize the editor to fit the text.
* @protected
*/
Blockly.FieldTextInput.prototype.resizeEditor_ = function() {
@@ -376,12 +389,30 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
var thisField = this;
return function() {
var htmlInput = Blockly.FieldTextInput.htmlInput_;
// Save the edit (if it validates).
thisField.maybeSaveEdit_();
// Finalize value.
thisField.isBeingEdited_ = false;
// No need to call setValue because if the widget is being closed the
// latest input text has already been validated.
if (thisField.value_ != thisField.text_) {
// At the end of an edit the text should be the same as the value. It
// may not be if the input text is different than the validated text.
// We should fix that.
thisField.text_ = String(thisField.value_);
thisField.isTextValid_ = true;
thisField.forceRerender();
}
// Otherwise don't rerender.
// Call onFinishEditing
// TODO: Get rid of this or make it less of a hack.
if (thisField.onFinishEditing_) {
thisField.onFinishEditing_(thisField.value_);
}
// Remove htmlInput.
thisField.unbindEvents_(htmlInput);
Blockly.FieldTextInput.htmlInput_ = null;
Blockly.Events.setGroup(false);
// Delete style properties.
var style = Blockly.WidgetDiv.DIV.style;
@@ -391,36 +422,11 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
};
};
/**
* Attempt to save the text field changes when the user input loses focus.
* If the value is not valid, revert to the default value.
* @private
*/
Blockly.FieldTextInput.prototype.maybeSaveEdit_ = function() {
var htmlInput = Blockly.FieldTextInput.htmlInput_;
// Save the edit (if it validates).
var text = htmlInput.value;
if (this.sourceBlock_) {
var text1 = this.callValidator(text);
if (text1 === null) {
// Invalid edit.
text = htmlInput.defaultValue;
} else {
// Validation function has changed the text.
text = text1;
if (this.onFinishEditing_) {
this.onFinishEditing_(text);
}
}
}
this.setText(text);
this.sourceBlock_.rendered && this.sourceBlock_.render();
};
/**
* Ensure that only a number may be entered.
* @param {string} text The user's text.
* @return {?string} A string representing a valid number, or null if invalid.
* @deprecated
*/
Blockly.FieldTextInput.numberValidator = function(text) {
console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' +
@@ -442,6 +448,7 @@ Blockly.FieldTextInput.numberValidator = function(text) {
* Ensure that only a nonnegative integer may be entered.
* @param {string} text The user's text.
* @return {?string} A string representing a valid int, or null if invalid.
* @deprecated
*/
Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) {
var n = Blockly.FieldTextInput.numberValidator(text);

View File

@@ -39,8 +39,9 @@ goog.require('goog.math.Size');
* Class for a variable's dropdown field.
* @param {?string} varname The default name for the variable. If null,
* a unique variable name will be generated.
* @param {Function=} opt_validator A function that is executed when a new
* option is selected. Its sole argument is the new option value.
* @param {Function=} opt_validator A function that is called to validate
* changes to the field's value. Takes in a variable ID & returns a
* validated variable ID, or null to abort the change.
* @param {Array.<string>=} opt_variableTypes A list of the types of variables
* to include in the dropdown.
* @param {string=} opt_defaultType The type of variable to create if this
@@ -78,6 +79,13 @@ Blockly.FieldVariable.fromJson = function(options) {
return new Blockly.FieldVariable(varname, null, variableTypes, defaultType);
};
/**
* The workspace that this variable field belongs to.
* @type {?Blockly.Workspace}
* @private
*/
Blockly.FieldVariable.prototype.workspace_ = null;
/**
* Serializable fields are saved by the XML renderer, non-serializable fields
* are not. Editable fields should also be serializable.
@@ -96,28 +104,28 @@ Blockly.FieldVariable.prototype.initModel = function() {
if (this.variable_) {
return; // Initialization already happened.
}
this.workspace_ = this.sourceBlock_.workspace;
var variable = Blockly.Variables.getOrCreateVariablePackage(
this.workspace_, null, this.defaultVariableName, this.defaultType_);
// Don't fire a change event for this setValue. It would have null as the
// old value, which is not valid.
Blockly.Events.disable();
try {
this.setValue(variable.getId());
} finally {
Blockly.Events.enable();
}
this.setValue(variable.getId());
Blockly.Events.enable();
};
/**
* Initialize this field based on the given XML.
* @param {!Element} fieldElement The element containing information about the
* variable field's state.
*/
Blockly.FieldVariable.prototype.fromXml = function(fieldElement) {
var id = fieldElement.getAttribute('id');
var variableName = fieldElement.textContent;
var variableType = fieldElement.getAttribute('variabletype') || '';
var variable = Blockly.Variables.getOrCreateVariablePackage(
this.workspace_ || this.sourceBlock_.workspace, id,
variableName, variableType);
this.workspace_, id, variableName, variableType);
// This should never happen :)
if (variableType != null && variableType !== variable.type) {
@@ -130,6 +138,12 @@ Blockly.FieldVariable.prototype.fromXml = function(fieldElement) {
this.setValue(variable.getId());
};
/**
* Serialize this field to Xml.
* @param {!Element} fieldElement The element to populate with info about the
* field's state.
* @return {!Element} The element containing info about the field's state.
*/
Blockly.FieldVariable.prototype.toXml = function(fieldElement) {
// Make sure the variable is initialized.
this.initModel();
@@ -160,6 +174,7 @@ Blockly.FieldVariable.prototype.setSourceBlock = function(block) {
throw Error('Variable fields are not allowed to exist on shadow blocks.');
}
Blockly.FieldVariable.superClass_.setSourceBlock.call(this, block);
this.workspace_ = block.workspace;
};
/**
@@ -192,30 +207,57 @@ Blockly.FieldVariable.prototype.getVariable = function() {
};
/**
* Set the variable ID.
* @param {string} id New variable ID, which must reference an existing
* variable.
* Gets the validation function for this field, or null if not set.
* Returns null if the variable is not set, because validators should not
* run on the initial setValue call, because the field won't be attached to
* a block and workspace at that point.
* @return {Function} Validation function, or null.
*/
Blockly.FieldVariable.prototype.setValue = function(id) {
var workspace = this.sourceBlock_.workspace;
var variable = Blockly.Variables.getVariable(workspace, id);
if (!variable) {
throw Error('Variable id doesn\'t point to a real variable! ID was ' + id);
Blockly.FieldVariable.prototype.getValidator = function() {
// Validators shouldn't operate on the initial setValue call.
// Normally this is achieved by calling setValidator after setValue, but
// this is not a possibility with variable fields.
if (this.variable_) {
return this.validator_;
}
// Type checks!
return null;
};
/**
* Ensure that the id belongs to a valid variable of an allowed type.
* @param {string} newId The id of the new variable to set.
* @return {?string} The validated id, or null if invalid.
* @protected
*/
Blockly.FieldVariable.prototype.doClassValidation_ = function(newId) {
var variable = Blockly.Variables.getVariable(this.workspace_, newId);
if (!variable) {
console.warn('Variable id doesn\'t point to a real variable! ' +
'ID was ' + newId);
return null;
}
// Type Checks.
var type = variable.type;
if (!this.typeIsAllowed_(type)) {
throw Error('Variable type doesn\'t match this field! Type was ' + type);
console.warn('Variable type doesn\'t match this field! Type was ' + type);
return null;
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
var oldValue = this.variable_ ? this.variable_.getId() : null;
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, oldValue, id));
}
this.variable_ = variable;
this.value_ = id;
this.setText(variable.name);
return newId;
};
/**
* Update the value of this variable field, as well as its variable and text.
*
* The variable ID should be valid at this point, but if a variable field
* validator returns a bad ID, this could break.
* @param {string} newId The id of the new variable.
* @protected
*/
Blockly.FieldVariable.prototype.doValueUpdate_ = function(newId) {
this.variable_ = Blockly.Variables.getVariable(this.workspace_, newId);
this.value_ = newId;
this.text_ = (this.variable_.name);
this.isDirty_ = true;
};
/**
@@ -248,9 +290,8 @@ Blockly.FieldVariable.prototype.getVariableTypes_ = function() {
var variableTypes = this.variableTypes;
if (variableTypes === null) {
// If variableTypes is null, return all variable types.
if (this.sourceBlock_) {
var workspace = this.sourceBlock_.workspace;
return workspace.getVariableTypes();
if (this.workspace_) {
return this.workspace_.getVariableTypes();
}
}
variableTypes = variableTypes || [''];
@@ -315,18 +356,14 @@ Blockly.FieldVariable.dropdownCreate = function() {
' variable selected.');
}
var name = this.getText();
var workspace = null;
if (this.sourceBlock_) {
workspace = this.sourceBlock_.workspace;
}
var variableModelList = [];
if (workspace) {
if (this.workspace_) {
var variableTypes = this.getVariableTypes_();
// Get a copy of the list, so that adding rename and new variable options
// doesn't modify the workspace's list.
for (var i = 0; i < variableTypes.length; i++) {
var variableType = variableTypes[i];
var variables = workspace.getVariablesOfType(variableType);
var variables = this.workspace_.getVariablesOfType(variableType);
variableModelList = variableModelList.concat(variables);
}
}
@@ -359,20 +396,19 @@ Blockly.FieldVariable.dropdownCreate = function() {
*/
Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
var id = menuItem.getValue();
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
var workspace = this.sourceBlock_.workspace;
// Handle special cases.
if (this.workspace_) {
if (id == Blockly.RENAME_VARIABLE_ID) {
// Rename variable.
Blockly.Variables.renameVariable(workspace, this.variable_);
Blockly.Variables.renameVariable(this.workspace_, this.variable_);
return;
} else if (id == Blockly.DELETE_VARIABLE_ID) {
// Delete variable.
workspace.deleteVariableById(this.variable_.getId());
this.workspace_.deleteVariableById(this.variable_.getId());
return;
}
// TODO (#1529): Call any validation function, and allow it to override.
}
// Handle unspecial case.
this.setValue(id);
};

View File

@@ -263,6 +263,21 @@ Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT
"tooltip": "",
"helpUrl": ""
},
{
"type": "test_fields_image",
"message0": "image %1",
"args0": [
{
"type": "field_image",
"name": "IMAGE",
"src": "https://blockly-demo.appspot.com/static/tests/media/a.png",
"width": 32,
"height": 32,
"alt": "A"
}
],
"colour": 230
},
{
"type": "test_numbers_float",
"message0": "float %1",
@@ -670,6 +685,420 @@ Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT
}
]); // END JSON EXTRACT (Do not delete this comment.)
Blockly.Blocks['test_validators_text_null'] = {
init: function() {
this.appendDummyInput()
.appendField("always null")
.appendField(new Blockly.FieldTextInput("default", this.validate), "INPUT");
this.setColour(230);
this.setCommentText('All input validates to null (invalid). The display' +
' text will remain the input text, but the value should be the default' +
' text. The input should be red after the first keystroke.');
},
validate: function(newValue) {
return null;
}
};
Blockly.Blocks['test_validators_text_A'] = {
init: function() {
this.appendDummyInput()
.appendField("remove \'a\'")
.appendField(new Blockly.FieldTextInput("default", this.validate), "INPUT");
this.setColour(230);
this.setCommentText('All \'a\' characters are removed from field value.' +
' The display text will include invalid \'a\' characters while the' +
' field is being edited, but the value will not.');
},
validate: function(newValue) {
return newValue.replace(/\a/g, '');
}
};
Blockly.Blocks['test_validators_text_B'] = {
init: function() {
this.appendDummyInput()
.appendField("\'b\' -> null")
.appendField(new Blockly.FieldTextInput("default", this.validate), "INPUT");
this.setColour(230);
this.setCommentText('Upon detecting a \'b\' character the input will' +
' validated to null (invalid). Upon removal it should revert to being' +
' valid. The display text will remain the input text, but if the input' +
' text is invalid the value should be the default text.');
},
validate: function(newValue) {
if (newValue.indexOf('b') != -1) {
return null;
}
return newValue;
}
};
Blockly.Blocks['test_validators_angle_null'] = {
init: function() {
this.appendDummyInput()
.appendField("always null")
.appendField(new Blockly.FieldAngle(90, this.validate), "INPUT");
this.setColour(230);
this.setCommentText('All input validates to null (invalid). The field' +
' will display the input while the field is being edited (this' +
' includes the text and the graphic), but the value should be the' +
' default value. The input should be red after the first' +
' keystroke.');
},
validate: function(newValue) {
return null;
}
};
Blockly.Blocks['test_validators_angle_mult30_force'] = {
init: function() {
this.appendDummyInput()
.appendField("force mult of 30")
.appendField(new Blockly.FieldAngle(90, this.validate), "INPUT");
this.setColour(230);
this.setCommentText('The input value will be rounded to the nearest' +
' multiple of 30. The field will display the input while the field is' +
' being edited (this includes the text and the graphic), but the value' +
' will be the validated (rounded) value. Note: If you want to do' +
' rounding this is not the proper way, use the ROUND property of the' +
' field angle instead.');
},
validate: function(newValue) {
return Math.round(newValue / 30) * 30;
}
};
Blockly.Blocks['test_validators_angle_mult30_null'] = {
init: function() {
this.appendDummyInput()
.appendField("not mult of 30 -> null")
.appendField(new Blockly.FieldAngle(90, this.validate), "INPUT");
this.setColour(230);
this.setCommentText('If the input value is not a multiple of 30, the' +
' input will validated to null (invalid). The field will display the' +
' input while the field is being edited (this includes the text and' +
' the graphic), but if the input value is invalid the value should be' +
' the default value.');
},
validate: function(newValue) {
if (newValue % 30 != 0) {
return null;
}
return newValue;
}
};
Blockly.Blocks['test_validators_checkbox_null'] = {
init: function() {
this.appendDummyInput()
.appendField("always null")
.appendField(new Blockly.FieldCheckbox(true, this.validate), "INPUT");
this.setColour(230);
this.setCommentText('The new input always validates to null (invalid).' +
' This means that the field value should not change.');
},
validate: function(newValue) {
return null;
}
};
Blockly.Blocks['test_validators_checkbox_match'] = {
init: function() {
this.appendDummyInput()
.appendField("force match")
.appendField(new Blockly.FieldCheckbox(true), "MATCH")
.appendField(new Blockly.FieldCheckbox(true, this.validate), "INPUT");
this.setColour(230);
this.setCommentText('The validator for this block only works on the' +
' end-most checkbox. The validator will always return the value of the' +
' start-most checkbox. Therefor they should always match.')
},
validate: function(newValue) {
return this.sourceBlock_.getFieldValue('MATCH');
}
};
Blockly.Blocks['test_validators_checkbox_not_match_null'] = {
init: function() {
this.appendDummyInput()
.appendField("not match -> null")
.appendField(new Blockly.FieldCheckbox(true), "MATCH")
.appendField(new Blockly.FieldCheckbox(true, this.validate), "INPUT");
this.setColour(230);
this.setCommentText('The validator for this block only works on the' +
' end-most checkbox. If the new value does not match the value of the' +
' start-most checkbox, it will return null (invalid), which means the' +
' field value should not change. Therfore they should always match.');
},
validate: function(newValue) {
if (this.sourceBlock_.getFieldValue('MATCH') != newValue) {
return null;
}
return newValue;
}
};
Blockly.Blocks['test_validators_colour_null'] = {
init: function() {
var colourField = new Blockly.FieldColour('#ff0000', this.validate);
colourField.setColours([
'#ffffff', '#ffdcdc', '#ffb4b4','#ff8c8c','#ff6464','#ff3c3c','#ff1414',
'#00ffff', '#00dcdc', '#00b4b4','#008c8c','#006464','#003c3c','#001414']);
this.appendDummyInput()
.appendField("always null")
.appendField(colourField, "INPUT");
this.setColour(230);
this.setCommentText('All input validates to null (invalid). This means' +
' the field value should not change.');
},
validate: function(newValue) {
return null;
}
};
Blockly.Blocks['test_validators_colour_force_red'] = {
init: function() {
var colourField = new Blockly.FieldColour('#ff0000', this.validate);
colourField.setColours([
'#ffffff', '#ffdcdc', '#ffb4b4','#ff8c8c','#ff6464','#ff3c3c','#ff1414',
'#00ffff', '#00dcdc', '#00b4b4','#008c8c','#006464','#003c3c','#001414']);
this.appendDummyInput()
.appendField("force full red")
.appendField(colourField, "INPUT");
this.setColour(230);
this.setCommentText('The input will have its red value replaced with' +
' full red.');
},
validate: function(newValue) {
return '#ff' + newValue.substr(3, 4);
}
};
Blockly.Blocks['test_validators_colour_red_null'] = {
init: function() {
var colourField = new Blockly.FieldColour('#ff0000', this.validate);
colourField.setColours([
'#ffffff', '#ffdcdc', '#ffb4b4','#ff8c8c','#ff6464','#ff3c3c','#ff1414',
'#00ffff', '#00dcdc', '#00b4b4','#008c8c','#006464','#003c3c','#001414']);
this.appendDummyInput()
.appendField("not red -> null")
.appendField(colourField, "INPUT");
this.setColour(230);
this.setCommentText('If the input does not have full red, the input will' +
' validate to null (invalid). Otherwise it will return the input value');
},
validate: function(newValue) {
if (newValue.substr(1, 2) != 'ff') {
return null;
}
return newValue;
}
};
Blockly.Blocks['test_validators_date_null'] = {
init: function() {
this.appendDummyInput()
.appendField("always null")
.appendField(new Blockly.FieldDate("2020-02-20", this.validate), "INPUT");
this.setColour(230);
this.setCommentText('All input validates to null (invalid). This means' +
' the field value should not change.');
},
validate: function(newValue) {
return null;
}
};
Blockly.Blocks['test_validators_date_force_20s'] = {
init: function() {
this.appendDummyInput()
.appendField("force day 20s")
.appendField(new Blockly.FieldDate("2020-02-20", this.validate), "INPUT");
this.setColour(230);
this.setCommentText('The input\'s date will change to always be in the' +
' 20s.');
},
validate: function(newValue) {
return newValue.substr(0, 8) + '2' + newValue.substr(9, 1);
}
};
Blockly.Blocks['test_validators_date_20s_null'] = {
init: function() {
this.appendDummyInput()
.appendField("not 20s -> null")
.appendField(new Blockly.FieldDate("2020-02-20", this.validate), "INPUT");
this.setColour(230);
this.setCommentText('If the input is not in the 20s, the input will' +
' validate to null (invalid). Otherwise it will return the input value.');
},
validate: function(newValue) {
if (newValue.charAt(8) != '2') {
return null;
}
return newValue;
}
};
Blockly.Blocks['test_validators_dropdown_null'] = {
init: function() {
this.appendDummyInput()
.appendField("always null")
.appendField(new Blockly.FieldDropdown([
["1a","1A"], ["1b","1B"], ["1c","1C"],
["2a","2A"], ["2b","2B"], ["2c","2C"]], this.validate), "INPUT");
this.setColour(230);
this.setCommentText('All input validates to null (invalid). This means' +
' the field value should not change.');
},
validate: function(newValue) {
return null;
}
};
Blockly.Blocks['test_validators_dropdown_force_1s'] = {
init: function() {
this.appendDummyInput()
.appendField("force 1s")
.appendField(new Blockly.FieldDropdown([
["1a","1A"], ["1b","1B"], ["1c","1C"],
["2a","2A"], ["2b","2B"], ["2c","2C"]], this.validate), "INPUT");
this.setColour(230);
this.setCommentText('The input\'s value will always change to start with' +
' 1.');
},
validate: function(newValue) {
return '1' + newValue.charAt(1);
}
};
Blockly.Blocks['test_validators_dropdown_1s_null'] = {
init: function() {
this.appendDummyInput()
.appendField("not 1s -> null")
.appendField(new Blockly.FieldDropdown([
["1a","1A"], ["1b","1B"], ["1c","1C"],
["2a","2A"], ["2b","2B"], ["2c","2C"]], this.validate), "INPUT");
this.setColour(230);
this.setCommentText('If the input does not start with 1, the input will' +
' validate to null (invalid). Otherwise it will return the input value.');
},
validate: function(newValue) {
if (newValue.charAt(0) != '1') {
return null;
}
return newValue;
}
};
Blockly.Blocks['test_validators_number_null'] = {
init: function() {
this.appendDummyInput()
.appendField("always null")
.appendField(new Blockly.FieldNumber(123, null, null, null, this.validate), "INPUT");
this.setColour(230);
this.setCommentText('All input validates to null (invalid). The field' +
' will display the input while the field is being edited, but the value' +
' should be the default value. The input should be red after the first' +
' keystroke.');
},
validate: function(newValue) {
return null;
}
};
Blockly.Blocks['test_validators_number_mult10_force'] = {
init: function() {
this.appendDummyInput()
.appendField("force mult of 10")
.appendField(new Blockly.FieldNumber(123, null, null, null, this.validate), "INPUT");
this.setColour(230);
this.setCommentText('Theinput value will be rounded to the nearest' +
' multiple of 10. The field will display the input while the field is' +
' being edited, but the value should be the validated (rounded) value.' +
' Note: If you want to do rounding this is not the proper way, use the' +
' precision option of the number field constructor instead.');
},
validate: function(newValue) {
return Math.round(newValue / 10) * 10;
}
};
Blockly.Blocks['test_validators_number_mult10_null'] = {
init: function() {
this.appendDummyInput()
.appendField("not mult of 10 -> null")
.appendField(new Blockly.FieldNumber(123, null, null, null, this.validate), "INPUT");
this.setColour(230);
this.setCommentText('If the input value is not a multiple of 10, the' +
' input will validate to null (invalid). The field will display the' +
' input while the field is being edited, but if the input value is' +
' invalid the value should be the default value.');
},
validate: function(newValue) {
if (newValue % 10 != 0) {
return null;
}
return newValue;
}
};
Blockly.Blocks['test_validators_variable_null'] = {
init: function() {
this.appendDummyInput()
.appendField("always null")
.appendField(new Blockly.FieldVariable('1a', this.validate), "INPUT");
this.setColour(230);
this.setCommentText('All ids validate to null (invalid). This means' +
' the variable should not change.');
},
validate: function(newValue) {
return null;
}
};
Blockly.Blocks['test_validators_variable_force_1s'] = {
init: function() {
this.appendDummyInput()
.appendField("force 1s")
.appendField(new Blockly.FieldVariable('1a', this.validate), "INPUT");
this.setColour(230);
this.setCommentText('The id will always change to start with 1.');
},
validate: function(newValue) {
return '1' + newValue.charAt(1);
}
};
Blockly.Blocks['test_validators_variable_1s_null'] = {
init: function() {
this.appendDummyInput()
.appendField("not 1s -> null")
.appendField(new Blockly.FieldVariable('1a', this.validate), "INPUT");
this.setColour(230);
this.setCommentText('If the id does not start with 1, the id will' +
' validate to null (invalid). Otherwise it will return the id.');
},
validate: function(newValue) {
if (newValue.charAt(0) != '1') {
return null;
}
return newValue;
}
};
Blockly.Blocks['test_basic_empty_with_mutator'] = {
init: function() {
this.setMutator(new Blockly.Mutator(['math_number']));

View File

@@ -1,44 +0,0 @@
/**
* @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 Tests for Blockly.FieldAngle
* @author Anm@anm.me (Andrew n marshall)
*/
'use strict';
function test_fieldangle_constructor() {
assertEquals(new Blockly.FieldAngle().getValue(), '0');
assertEquals(new Blockly.FieldAngle(null).getValue(), '0');
assertEquals(new Blockly.FieldAngle(undefined).getValue(), '0');
assertEquals(new Blockly.FieldAngle(1).getValue(), '1');
assertEquals(new Blockly.FieldAngle(1.5).getValue(), '1.5');
assertEquals(new Blockly.FieldAngle('2').getValue(), '2');
assertEquals(new Blockly.FieldAngle('2.5').getValue(), '2.5');
// Bad values
assertEquals(new Blockly.FieldAngle('bad').getValue(), '0');
assertEquals(new Blockly.FieldAngle(NaN).getValue(), '0');
}
function test_fieldangle_fromJson() {
assertEquals(Blockly.FieldAngle.fromJson({}).getValue(), '0');
assertEquals(Blockly.FieldAngle.fromJson({ angle: 1 }).getValue(), '1');
}

View File

@@ -1,80 +0,0 @@
/**
* @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 Tests for Blockly.FieldNumber
* @author Anm@anm.me (Andrew n marshall)
*/
'use strict';
function test_fieldnumber_constructor() {
// No arguments
var field = new Blockly.FieldNumber();
assertEquals(field.getValue(), '0');
assertEquals(field.min_, -Infinity);
assertEquals(field.max_, Infinity);
assertEquals(field.precision_, 0);
// Numeric values
field = new Blockly.FieldNumber(1);
assertEquals(field.getValue(), '1');
field = new Blockly.FieldNumber(1.5);
assertEquals(field.getValue(), '1.5');
// String value
field = new Blockly.FieldNumber('2');
assertEquals(field.getValue(), '2');
field = new Blockly.FieldNumber('2.5');
assertEquals(field.getValue(), '2.5');
// All values
field = new Blockly.FieldNumber(
/* value */ 0,
/* min */ -128,
/* max */ 127,
/* precision */ 1);
assertEquals(field.getValue(), '0');
assertEquals(field.min_, -128);
assertEquals(field.max_, 127);
assertEquals(field.precision_, 1);
// Bad value defaults to '0'
field = new Blockly.FieldNumber('bad');
assertEquals(field.getValue(), '0');
field = new Blockly.FieldNumber(NaN);
assertEquals(field.getValue(), '0');
}
function test_fieldnumber_fromJson() {
assertEquals(Blockly.FieldNumber.fromJson({}).getValue(), '0');
assertEquals(Blockly.FieldNumber.fromJson({ value: 1 }).getValue(), '1');
// All options
var field = Blockly.FieldNumber.fromJson({
value: 0,
min: -128,
max: 127,
precision: 1
});
assertEquals(field.getValue(), '0');
assertEquals(field.min_, -128);
assertEquals(field.max_, 127);
assertEquals(field.precision_, 1);
}

View File

@@ -1,237 +0,0 @@
/**
* @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 Tests for Blockly.FieldVariable
* @author marisaleung@google.com (Marisa Leung)
*/
'use strict';
goog.require('goog.testing');
goog.require('goog.testing.MockControl');
var workspace;
var mockControl_;
function fieldVariableTestWithMocks_setUp() {
workspace = new Blockly.Workspace();
mockControl_ = new goog.testing.MockControl();
}
function fieldVariableTestWithMocks_tearDown() {
mockControl_.$tearDown();
workspace.dispose();
}
function fieldVariable_mockBlock(workspace) {
return {'workspace': workspace, 'isShadow': function(){return false;}};
}
function fieldVariable_createAndInitField(workspace) {
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = fieldVariable_mockBlock(workspace);
fieldVariable.setSourceBlock(mockBlock);
// No view to initialize, but the model still needs work.
fieldVariable.initModel();
return fieldVariable;
}
function test_fieldVariable_Constructor() {
workspace = new Blockly.Workspace();
var fieldVariable = new Blockly.FieldVariable('name1');
// The field does not have a variable until after init() is called.
assertEquals('', fieldVariable.getText());
workspace.dispose();
}
function test_fieldVariable_setValueMatchId() {
// Expect the fieldVariable value to be set to variable name
fieldVariableTestWithMocks_setUp();
workspace.createVariable('name2', null, 'id2');
var fieldVariable = fieldVariable_createAndInitField(workspace);
var oldId = fieldVariable.getValue();
var event = new Blockly.Events.BlockChange(
fieldVariable.sourceBlock_, 'field', undefined, oldId, 'id2');
setUpMockMethod(mockControl_, Blockly.Events, 'fire', [event], null);
fieldVariable.setValue('id2');
assertEquals('name2', fieldVariable.getText());
assertEquals('id2', fieldVariable.getValue());
fieldVariableTestWithMocks_tearDown();
}
function test_fieldVariable_setValueNoVariable() {
fieldVariableTestWithMocks_setUp();
var fieldVariable = fieldVariable_createAndInitField(workspace);
var mockBlock = fieldVariable.sourceBlock_;
mockBlock.isShadow = function() {
return false;
};
try {
fieldVariable.setValue('id1');
// Calling setValue with a variable that doesn't exist throws an error.
fail();
} catch (e) {
// expected
} finally {
fieldVariableTestWithMocks_tearDown();
}
}
function test_fieldVariable_dropdownCreateVariablesExist() {
// Expect that the dropdown options will contain the variables that exist.
workspace = new Blockly.Workspace();
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
var fieldVariable = fieldVariable_createAndInitField(workspace);
var result_options = Blockly.FieldVariable.dropdownCreate.call(
fieldVariable);
assertEquals(result_options.length, 3);
isEqualArrays(result_options[0], ['name1', 'id1']);
isEqualArrays(result_options[1], ['name2', 'id2']);
workspace.dispose();
}
function test_fieldVariable_setValueNull() {
// This should no longer create a variable for the selected option.
fieldVariableTestWithMocks_setUp();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['id1', null]);
var fieldVariable = fieldVariable_createAndInitField(workspace);
try {
fieldVariable.setValue(null);
fail();
} catch (e) {
// expected
} finally {
fieldVariableTestWithMocks_tearDown();
}
}
function test_fieldVariable_getVariableTypes_undefinedVariableTypes() {
// Expect that since variableTypes is undefined, only type empty string
// will be returned (regardless of what types are available on the workspace).
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable('name1');
var resultTypes = fieldVariable.getVariableTypes_();
isEqualArrays(resultTypes, ['']);
workspace.dispose();
}
function test_fieldVariable_getVariableTypes_givenVariableTypes() {
// Expect that since variableTypes is defined, it will be the return value,
// regardless of what types are available on the workspace.
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable(
'name1', null, ['type1', 'type2'], 'type1');
var resultTypes = fieldVariable.getVariableTypes_();
isEqualArrays(resultTypes, ['type1', 'type2']);
assertEquals('Default type was wrong', 'type1', fieldVariable.defaultType_);
workspace.dispose();
}
function test_fieldVariable_getVariableTypes_nullVariableTypes() {
// Expect all variable types to be returned.
// The variable does not need to be initialized to do this--it just needs a
// pointer to the workspace.
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = fieldVariable_mockBlock(workspace);
fieldVariable.setSourceBlock(mockBlock);
fieldVariable.variableTypes = null;
var resultTypes = fieldVariable.getVariableTypes_();
// The empty string is always one of the options.
isEqualArrays(resultTypes, ['type1', 'type2', '']);
workspace.dispose();
}
function test_fieldVariable_getVariableTypes_emptyListVariableTypes() {
// Expect an error to be thrown.
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = fieldVariable_mockBlock(workspace);
fieldVariable.setSourceBlock(mockBlock);
fieldVariable.variableTypes = [];
try {
fieldVariable.getVariableTypes_();
fail();
} catch (e) {
//expected
} finally {
workspace.dispose();
}
}
function test_fieldVariable_defaultType_exists() {
var fieldVariable = new Blockly.FieldVariable(null, null, ['b'], 'b');
assertEquals('The variable field\'s default type should be "b"',
'b', fieldVariable.defaultType_);
}
function test_fieldVariable_noDefaultType() {
var fieldVariable = new Blockly.FieldVariable(null);
assertEquals('The variable field\'s default type should be the empty string',
'', fieldVariable.defaultType_);
assertNull('The variable field\'s allowed types should be null',
fieldVariable.variableTypes);
}
function test_fieldVariable_defaultTypeMismatch() {
try {
var fieldVariable = new Blockly.FieldVariable(null, null, ['a'], 'b');
fail('Variable field creation should have failed due to an invalid ' +
'default type');
} catch (e) {
// expected
}
}
function test_fieldVariable_defaultTypeMismatch_empty() {
try {
var fieldVariable = new Blockly.FieldVariable(null, null, ['a']);
fail('Variable field creation should have failed due to an invalid ' +
'default type');
} catch (e) {
// expected
}
}

View File

@@ -18,10 +18,7 @@
<script src="connection_test.js"></script>
<script src="event_test.js"></script>
<script src="extensions_test.js"></script>
<script src="field_angle_test.js"></script>
<script src="field_number_test.js"></script>
<script src="field_test.js"></script>
<script src="field_variable_test.js"></script>
<script src="generator_test.js"></script>
<script src="gesture_test.js"></script>
<script src="input_test.js"></script>

View File

@@ -127,7 +127,7 @@ suite ('Angle Fields', function() {
this.angleField.setValue(undefined);
assertValueDefault(this.angleField);
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
this.angleField.setValue('bad');
assertValueDefault(this.angleField);
});
@@ -164,15 +164,15 @@ suite ('Angle Fields', function() {
this.angleField.setValue(null);
assertValue(this.angleField, 1);
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.angleField.setValue(undefined);
assertValue(this.angleField, 1);
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
this.angleField.setValue('bad');
assertValue(this.angleField, 1);
});
test.skip('NaN', function() {
test('NaN', function() {
this.angleField.setValue(NaN);
assertValue(this.angleField, 1);
});
@@ -198,7 +198,7 @@ suite ('Angle Fields', function() {
});
});
});
suite.skip('Validators', function() {
suite('Validators', function() {
setup(function() {
this.angleField = new Blockly.FieldAngle(1);
Blockly.FieldTextInput.htmlInput_ = Object.create(null);

View File

@@ -18,7 +18,7 @@
* limitations under the License.
*/
suite.skip('Checkbox Fields', function() {
suite('Checkbox Fields', function() {
function assertValue(checkboxField, expectedValue, expectedText) {
var actualValue = checkboxField.getValue();
var actualText = checkboxField.getText();

View File

@@ -57,11 +57,11 @@ suite ('Colour Fields', function() {
var colourField = new Blockly.FieldColour(undefined);
assertValueDefault(colourField);
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
var colourField = new Blockly.FieldColour('bad');
assertValueDefault(colourField);
});
test.skip('#AAAAAA', function() {
test('#AAAAAA', function() {
var colourField = new Blockly.FieldColour('#AAAAAA');
assertValue(colourField, '#aaaaaa', '#aaa');
});
@@ -69,7 +69,7 @@ suite ('Colour Fields', function() {
var colourField = new Blockly.FieldColour('#aaaaaa');
assertValue(colourField, '#aaaaaa', '#aaa');
});
test.skip('#AAAA00', function() {
test('#AAAA00', function() {
var colourField = new Blockly.FieldColour('#AAAA00');
assertValue(colourField, '#aaaa00', '#aa0');
});
@@ -77,7 +77,7 @@ suite ('Colour Fields', function() {
var colourField = new Blockly.FieldColour('#aaaa00');
assertValue(colourField, '#aaaa00', '#aa0');
});
test.skip('#BCBCBC', function() {
test('#BCBCBC', function() {
var colourField = new Blockly.FieldColour('#BCBCBC');
assertValue(colourField, '#bcbcbc', '#bcbcbc');
});
@@ -99,11 +99,11 @@ suite ('Colour Fields', function() {
var colourField = new Blockly.FieldColour.fromJson({ colour:undefined });
assertValueDefault(colourField);
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
var colourField = new Blockly.FieldColour.fromJson({ colour:'bad' });
assertValueDefault(colourField);
});
test.skip('#AAAAAA', function() {
test('#AAAAAA', function() {
var colourField = Blockly.FieldColour.fromJson({ colour: '#AAAAAA' });
assertValue(colourField, '#aaaaaa', '#aaa');
});
@@ -111,7 +111,7 @@ suite ('Colour Fields', function() {
var colourField = Blockly.FieldColour.fromJson({ colour: '#aaaaaa' });
assertValue(colourField, '#aaaaaa', '#aaa');
});
test.skip('#AAAA00', function() {
test('#AAAA00', function() {
var colourField = Blockly.FieldColour.fromJson({ colour: '#AAAA00' });
assertValue(colourField, '#aaaa00', '#aa0');
});
@@ -119,7 +119,7 @@ suite ('Colour Fields', function() {
var colourField = Blockly.FieldColour.fromJson({ colour: '#aaaa00' });
assertValue(colourField, '#aaaa00', '#aa0');
});
test.skip('#BCBCBC', function() {
test('#BCBCBC', function() {
var colourField = Blockly.FieldColour.fromJson({ colour: '#BCBCBC' });
assertValue(colourField, '#bcbcbc', '#bcbcbc');
});
@@ -133,15 +133,15 @@ suite ('Colour Fields', function() {
setup(function() {
this.colourField = new Blockly.FieldColour();
});
test.skip('Null', function() {
test('Null', function() {
this.colourField.setValue(null);
assertValueDefault(this.colourField);
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.colourField.setValue(undefined);
assertValueDefault(this.colourField);
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
this.colourField.setValue('bad');
assertValueDefault(this.colourField);
});
@@ -158,15 +158,15 @@ suite ('Colour Fields', function() {
setup(function() {
this.colourField = new Blockly.FieldColour('#aaaaaa');
});
test.skip('Null', function() {
test('Null', function() {
this.colourField.setValue(null);
assertValue(this.colourField, '#aaaaaa', '#aaa');
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.colourField.setValue(undefined);
assertValue(this.colourField, '#aaaaaa', '#aaa');
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
this.colourField.setValue('bad');
assertValue(this.colourField, '#aaaaaa', '#aaa');
});
@@ -180,7 +180,7 @@ suite ('Colour Fields', function() {
});
});
});
suite.skip('Validators', function() {
suite('Validators', function() {
setup(function() {
this.colourField = new Blockly.FieldColour('#aaaaaa');
});

View File

@@ -42,7 +42,7 @@ suite ('Date Fields', function() {
var dateField = new Blockly.FieldDate(undefined);
assertValueDefault(dateField);
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
var dateField = new Blockly.FieldDate('bad');
assertValueDefault(dateField);
});
@@ -50,11 +50,11 @@ suite ('Date Fields', function() {
var dateField = new Blockly.FieldDate('2020-02-20');
assertValue(dateField, '2020-02-20');
});
test.skip('Invalid Date - Month(2020-13-20)', function() {
test('Invalid Date - Month(2020-13-20)', function() {
var dateField = new Blockly.FieldDate('2020-13-20');
assertValueDefault(dateField);
});
test.skip('Invalid Date - Day(2020-02-32)', function() {
test('Invalid Date - Day(2020-02-32)', function() {
var dateField = new Blockly.FieldDate('2020-02-32');
assertValueDefault(dateField);
});
@@ -72,7 +72,7 @@ suite ('Date Fields', function() {
var dateField = Blockly.FieldDate.fromJson({ date: undefined });
assertValueDefault(dateField);
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
var dateField = Blockly.FieldDate.fromJson({ date: 'bad' });
assertValueDefault(dateField);
});
@@ -80,11 +80,11 @@ suite ('Date Fields', function() {
var dateField = Blockly.FieldDate.fromJson({ date: '2020-02-20' });
assertValue(dateField, '2020-02-20');
});
test.skip('Invalid Date - Month(2020-13-20)', function() {
test('Invalid Date - Month(2020-13-20)', function() {
var dateField = Blockly.FieldDate.fromJson({ date: '2020-13-20' });
assertValueDefault(dateField);
});
test.skip('Invalid Date - Day(2020-02-32)', function() {
test('Invalid Date - Day(2020-02-32)', function() {
var dateField = Blockly.FieldDate.fromJson({ date: '2020-02-32' });
assertValueDefault(dateField);
});
@@ -94,23 +94,23 @@ suite ('Date Fields', function() {
setup(function() {
this.dateField = new Blockly.FieldDate();
});
test.skip('Null', function() {
test('Null', function() {
this.dateField.setValue(null);
assertValueDefault(this.dateField);
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.dateField.setValue(undefined);
assertValueDefault(this.dateField);
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
this.dateField.setValue('bad');
assertValueDefault(this.dateField);
});
test.skip('Invalid Date - Month(2020-13-20)', function() {
test('Invalid Date - Month(2020-13-20)', function() {
this.dateField.setValue('2020-13-20');
assertValueDefault(this.dateField);
});
test.skip('Invalid Date - Day(2020-02-32)', function() {
test('Invalid Date - Day(2020-02-32)', function() {
this.dateField.setValue('2020-02-32');
assertValueDefault(this.dateField);
});
@@ -123,23 +123,23 @@ suite ('Date Fields', function() {
setup(function() {
this.dateField = new Blockly.FieldDate('2020-02-20');
});
test.skip('Null', function() {
test('Null', function() {
this.dateField.setValue(null);
assertValue(this.dateField, '2020-02-20');
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.dateField.setValue(undefined);
assertValue(this.dateField, '2020-02-20');
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
this.dateField.setValue('bad');
assertValue(this.dateField, '2020-02-20');
});
test.skip('Invalid Date - Month(2020-13-20)', function() {
test('Invalid Date - Month(2020-13-20)', function() {
this.dateField.setValue('2020-13-20');
assertValue(this.dateField, '2020-02-20');
});
test.skip('Invalid Date - Day(2020-02-32)', function() {
test('Invalid Date - Day(2020-02-32)', function() {
this.dateField.setValue('2020-02-32');
assertValue(this.dateField, '2020-02-20');
});
@@ -149,7 +149,7 @@ suite ('Date Fields', function() {
});
});
});
suite.skip('Validators', function() {
suite('Validators', function() {
setup(function() {
this.dateField = new Blockly.FieldDate('2020-02-20');
});

View File

@@ -148,11 +148,11 @@ suite ('Dropdown Fields', function() {
this.dropdownField.setValue(null);
assertValue(this.dropdownField, 'A', 'a');
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.dropdownField.setValue(undefined);
assertValue(this.dropdownField, 'A', 'a');
});
test.skip('Invalid ID', function() {
test('Invalid ID', function() {
this.dropdownField.setValue('bad');
assertValue(this.dropdownField, 'A', 'a');
});
@@ -161,7 +161,7 @@ suite ('Dropdown Fields', function() {
assertValue(this.dropdownField, 'B', 'b');
});
});
suite.skip('Validators', function() {
suite('Validators', function() {
setup(function() {
this.dropdownField = new Blockly.FieldDropdown([
["1a","1A"], ["1b","1B"], ["1c","1C"],

View File

@@ -149,7 +149,7 @@ suite ('Image Fields', function() {
this.imageField.setValue(null);
assertValue(this.imageField, 'src', 'alt');
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.imageField.setValue(undefined);
assertValue(this.imageField, 'src', 'alt');
});

View File

@@ -108,7 +108,7 @@ suite ('Label Serializable Fields', function() {
this.labelField.setValue(null);
assertValueDefault(this.labelField);
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.labelField.setValue(undefined);
assertValueDefault(this.labelField);
});
@@ -120,7 +120,7 @@ suite ('Label Serializable Fields', function() {
this.labelField.setValue(1);
assertValue(this.labelField, '1');
});
test.skip('Number (Falsy)', function() {
test('Number (Falsy)', function() {
this.labelField.setValue(0);
assertValue(this.labelField, '0');
});
@@ -128,7 +128,7 @@ suite ('Label Serializable Fields', function() {
this.labelField.setValue(true);
assertValue(this.labelField, 'true');
});
test.skip('Boolean False', function() {
test('Boolean False', function() {
this.labelField.setValue(false);
assertValue(this.labelField, 'false');
});
@@ -141,7 +141,7 @@ suite ('Label Serializable Fields', function() {
this.labelField.setValue(null);
assertValue(this.labelField, 'value');
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.labelField.setValue(undefined);
assertValue(this.labelField, 'value');
});

View File

@@ -105,7 +105,7 @@ suite ('Label Fields', function() {
this.labelField.setValue(null);
assertValueDefault(this.labelField);
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.labelField.setValue(undefined);
assertValueDefault(this.labelField);
});
@@ -117,7 +117,7 @@ suite ('Label Fields', function() {
this.labelField.setValue(1);
assertValue(this.labelField, '1');
});
test.skip('Number (Falsy)', function() {
test('Number (Falsy)', function() {
this.labelField.setValue(0);
assertValue(this.labelField, '0');
});
@@ -125,7 +125,7 @@ suite ('Label Fields', function() {
this.labelField.setValue(true);
assertValue(this.labelField, 'true');
});
test.skip('Boolean False', function() {
test('Boolean False', function() {
this.labelField.setValue(false);
assertValue(this.labelField, 'false');
});
@@ -138,7 +138,7 @@ suite ('Label Fields', function() {
this.labelField.setValue(null);
assertValue(this.labelField, 'value');
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.labelField.setValue(undefined);
assertValue(this.labelField, 'value');
});

View File

@@ -136,15 +136,15 @@ suite ('Number Fields', function() {
this.numberField.setValue(null);
assertValueDefault(this.numberField);
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.numberField.setValue(undefined);
assertValueDefault(this.numberField);
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
this.numberField.setValue('bad');
assertValueDefault(this.numberField);
});
test.skip('NaN', function() {
test('NaN', function() {
this.numberField.setValue(NaN);
assertValueDefault(this.numberField);
});
@@ -173,15 +173,15 @@ suite ('Number Fields', function() {
this.numberField.setValue(null);
assertValue(this.numberField, 1);
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.numberField.setValue(undefined);
assertValue(this.numberField, 1);
});
test.skip('Non-Parsable String', function() {
test('Non-Parsable String', function() {
this.numberField.setValue('bad');
assertValue(this.numberField, 1);
});
test.skip('NaN', function() {
test('NaN', function() {
this.numberField.setValue(NaN);
assertValue(this.numberField, 1);
});
@@ -228,7 +228,7 @@ suite ('Number Fields', function() {
numberField.setValue(123.456);
assertValue(numberField, 123);
});
test.skip('1.5', function() {
test('1.5', function() {
var numberField = new Blockly.FieldNumber
.fromJson({ precision: 1.5 });
numberField.setValue(123.456);
@@ -295,7 +295,7 @@ suite ('Number Fields', function() {
});
});
});
suite.skip('Validators', function() {
suite('Validators', function() {
setup(function() {
this.numberFieldField = new Blockly.FieldNumber(1);
Blockly.FieldTextInput.htmlInput_ = Object.create(null);

View File

@@ -107,7 +107,7 @@ suite ('Text Input Fields', function() {
this.textInputField.setValue(null);
assertValueDefault(this.textInputField);
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.textInputField.setValue(undefined);
assertValueDefault(this.textInputField);
});
@@ -119,7 +119,7 @@ suite ('Text Input Fields', function() {
this.textInputField.setValue(1);
assertValue(this.textInputField, '1');
});
test.skip('Number (Falsy)', function() {
test('Number (Falsy)', function() {
this.textInputField.setValue(0);
assertValue(this.textInputField, '0');
});
@@ -127,7 +127,7 @@ suite ('Text Input Fields', function() {
this.textInputField.setValue(true);
assertValue(this.textInputField, 'true');
});
test.skip('Boolean False', function() {
test('Boolean False', function() {
this.textInputField.setValue(false);
assertValue(this.textInputField, 'false');
});
@@ -140,7 +140,7 @@ suite ('Text Input Fields', function() {
this.textInputField.setValue(null);
assertValue(this.textInputField, 'value');
});
test.skip('Undefined', function() {
test('Undefined', function() {
this.textInputField.setValue(undefined);
assertValue(this.textInputField, 'value');
});
@@ -166,7 +166,7 @@ suite ('Text Input Fields', function() {
});
});
});
suite.skip('Validators', function() {
suite('Validators', function() {
setup(function() {
this.textInputField = new Blockly.FieldTextInput('value');
Blockly.FieldTextInput.htmlInput_ = Object.create(null);

View File

@@ -127,13 +127,13 @@ suite('Variable Fields', function() {
});
});
suite('setValue', function() {
test.skip('Null', function() {
test('Null', function() {
var variableField = createAndInitFieldConstructor(
this.workspace, 'name1');
variableField.setValue(null);
assertValue(variableField, 'name1');
});
test.skip('Undefined', function() {
test('Undefined', function() {
var variableField = createAndInitFieldConstructor(
this.workspace, 'name1');
variableField.setValue(undefined);
@@ -152,14 +152,14 @@ suite('Variable Fields', function() {
assertEquals('id2', variableField.getValue());
chai.assert.notEqual(oldId, variableField.getValue());
});
test.skip('Variable Does not Exist', function() {
test('Variable Does not Exist', function() {
var variableField = createAndInitFieldConstructor(
this.workspace, 'name1');
variableField.setValue('id1');
assertValue(variableField, 'name1');
});
});
suite.skip('Validators', function() {
suite('Validators', function() {
setup(function() {
this.workspace.createVariable('name1', null, 'id1');
this.workspace.createVariable('name2', null, 'id2');

View File

@@ -144,6 +144,15 @@ function start() {
}
function addToolboxButtonCallbacks() {
var addAllBlocksToWorkspace = function(button) {
var workspace = button.getTargetWorkspace();
var blocks = button.workspace_.getTopBlocks();
for(var i = 0, block; block = blocks[i]; i++) {
var xml = Blockly.Xml.textToDom('<xml></xml>');
xml.appendChild(Blockly.Xml.blockToDom(block));
Blockly.Xml.appendDomToWorkspace(xml, workspace);
}
};
var randomizeLabelText = function(button) {
var blocks = button.targetWorkspace_
.getBlocksByType('test_fields_label_serializable');
@@ -177,13 +186,59 @@ function addToolboxButtonCallbacks() {
block.setShadow(!block.isShadow());
}
};
var toggleCollapsed = function(button) {
var blocks = button.workspace_.getAllBlocks();
for(var i = 0, block; block = blocks[i]; i++) {
block.setCollapsed(!block.isCollapsed());
}
};
var setInput = function(button) {
Blockly.prompt('Input text to set.', 'ab', function(input) {
var blocks = button.getTargetWorkspace().getAllBlocks();
for(var i = 0, block; block = blocks[i]; i++) {
if (block.getField('INPUT')) {
block.setFieldValue(input, 'INPUT');
}
}
})
};
var changeImage = function(button) {
var blocks = button.workspace_.getBlocksByType('test_fields_image');
var possible = 'abcdefghijklm';
var image = possible.charAt(Math.floor(Math.random() * possible.length));
var src = 'https://blockly-demo.appspot.com/static/tests/media/'
+ image + '.png';
for (var i = 0, block; block = blocks[i]; i++) {
var imageField = block.getField('IMAGE');
imageField.setValue(src);
imageField.setText(image);
}
};
var addVariables = function(button) {
workspace.createVariable('1a', '', '1A');
workspace.createVariable('1b', '', '1B');
workspace.createVariable('1c', '', '1C');
workspace.createVariable('2a', '', '2A');
workspace.createVariable('2b', '', '2B');
workspace.createVariable('2c', '', '2C');
};
workspace.registerButtonCallback(
'addVariables', addVariables);
workspace.registerButtonCallback(
'changeImage', changeImage);
workspace.registerButtonCallback(
'addAllBlocksToWorkspace', addAllBlocksToWorkspace);
workspace.registerButtonCallback(
'setInput', setInput);
workspace.registerButtonCallback(
'setRandomStyle', setRandomStyle);
workspace.registerButtonCallback(
'toggleEnabled', toggleEnabled);
workspace.registerButtonCallback(
'toggleShadow', toggleShadow);
workspace.registerButtonCallback(
'toggleCollapsed', toggleCollapsed);
workspace.registerButtonCallback(
'randomizeLabelText', randomizeLabelText);
workspace.registerButtonCallback(
@@ -1236,11 +1291,15 @@ h1 {
</category>
<category name="Fields" expanded="true">
<category name="Defaults">
<button text="add blocks to workspace" callbackKey="addAllBlocksToWorkspace"></button>
<sep gap="8"></sep>
<button text="set random style" callbackKey="setRandomStyle"></button>
<sep gap="8"></sep>
<button text="toggle enabled" callbackKey="toggleEnabled"></button>
<sep gap="8"></sep>
<button text="toggle shadow" callbackKey="toggleShadow"></button>
<sep gap="8"></sep>
<button text="toggle collapsed" callbackKey="toggleCollapsed"></button>
<block type="test_fields_angle"></block>
<block type="test_fields_date"></block>
<block type="test_fields_checkbox"></block>
@@ -1248,7 +1307,11 @@ h1 {
<block type="test_fields_text_input"></block>
<block type="test_fields_variable"></block>
<button text="randomize label text" callbackKey="randomizeLabelText"></button>
<sep gap="12"></sep>
<block type="test_fields_label_serializable"></block>
<button text="change image" callbackKey="changeImage"></button>
<sep gap="12"></sep>
<block type="test_fields_image"></block>
</category>
<category name="Numbers">
<block type="test_numbers_float">
@@ -1298,6 +1361,69 @@ h1 {
<field name="TEXT">Zalgo in text field: B&#776;&#788;&#862;&#795;&#842;&#827;&#806;&#837;&#812;&#792;&#816;&#846;&#805;l&#771;&#832;&#833;&#864;&#849;&#849;&#789;&#861;&#801;&#854;&#863;&#811;&#826;&#812;&#790;&#803;&#819;o&#843;&#777;&#778;&#785;&#831;&#829;&#794;&#825;&#857;&#814;&#802;&#811;&#852;c&#843;&#786;&#849;&#778;&#861;&#775;&#825;&#825;&#796;&#857;&#825;&#800;&#824;k&#778;&#850;&#833;&#774;&#772;&#782;&#862;&#770;&#789;&#788;&#841;&#801;&#811;&#860;&#839;&#790;&#819;&#854;l&#832;&#774;&#836;&#831;&#776;&#787;&#855;&#816;&#793;&#798;&#819;&#809;&#800;&#854;&#815;y&#864;&#783;&#856;&#773;&#832;&#808;&#799;&#839;&#814;&#840;&#812;&#793;&#818;&#801;</field>
</block>
</category>
<category name="Validators">
<button text="add blocks to workspace" callbackKey="addAllBlocksToWorkspace"></button>
<sep gap="8"></sep>
<button text="set input" callbackKey="setInput"></button>
<label text="Angles"></label>
<sep gap="12"></sep>
<block type="test_validators_angle_null"></block>
<sep gap="12"></sep>
<block type="test_validators_angle_mult30_force"></block>
<sep gap="12"></sep>
<block type="test_validators_angle_mult30_null"></block>
<label text="Checkboxes"></label>
<sep gap="12"></sep>
<block type="test_validators_checkbox_null"></block>
<sep gap="12"></sep>
<block type="test_validators_checkbox_match"></block>
<sep gap="12"></sep>
<block type="test_validators_checkbox_not_match_null"></block>
<label text="Colours"></label>
<sep gap="12"></sep>
<block type="test_validators_colour_null"></block>
<sep gap="12"></sep>
<block type="test_validators_colour_force_red"></block>
<sep gap="12"></sep>
<block type="test_validators_colour_red_null"></block>
<label text="Dates"></label>
<sep gap="12"></sep>
<block type="test_validators_date_null"></block>
<sep gap="12"></sep>
<block type="test_validators_date_force_20s"></block>
<sep gap="12"></sep>
<block type="test_validators_date_20s_null"></block>
<label text="Dropdowns"></label>
<sep gap="12"></sep>
<block type="test_validators_dropdown_null"></block>
<sep gap="12"></sep>
<block type="test_validators_dropdown_force_1s"></block>
<sep gap="12"></sep>
<block type="test_validators_dropdown_1s_null"></block>
<label text="Numbers"></label>
<sep gap="12"></sep>
<block type="test_validators_number_null"></block>
<sep gap="12"></sep>
<block type="test_validators_number_mult10_force"></block>
<sep gap="12"></sep>
<block type="test_validators_number_mult10_null"></block>
<label text="Text"></label>
<sep gap="12"></sep>
<block type="test_validators_text_null"></block>
<sep gap="12"></sep>
<block type="test_validators_text_A"></block>
<sep gap="12"></sep>
<block type="test_validators_text_B"></block>
<label text="Variables"></label>
<sep gap="8"></sep>
<button text="add test variables" callbackKey="addVariables" web-class="modifiesWorkspace"></button>
<sep gap="12"></sep>
<block type="test_validators_variable_null"></block>
<sep gap="12"></sep>
<block type="test_validators_variable_force_1s"></block>
<sep gap="12"></sep>
<block type="test_validators_variable_1s_null"></block>
</category>
</category>
<category name="Mutators">
<label text="logic_compare"></label>