Customising field types using a register of fields #1584 (#1594)

Implement #1584 - Fields now registered by their JSON type name, allowing new custom fields and overriding of the standard fields. Replaces the manual switch statement for loading fields from JSON block definitions.
This commit is contained in:
Mark Gibson
2018-02-14 18:14:59 +00:00
committed by Andrew n marshall
parent acaeeb4d2e
commit 54f0e06e21
13 changed files with 106 additions and 36 deletions

View File

@@ -1231,44 +1231,21 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
case 'input_dummy':
input = this.appendDummyInput(element['name']);
break;
case 'field_label':
field = Blockly.FieldLabel.fromJson(element);
break;
case 'field_input':
field = Blockly.FieldTextInput.fromJson(element);
break;
case 'field_angle':
field = Blockly.FieldAngle.fromJson(element);
break;
case 'field_checkbox':
field = Blockly.FieldCheckbox.fromJson(element);
break;
case 'field_colour':
field = Blockly.FieldColour.fromJson(element);
break;
case 'field_variable':
field = Blockly.FieldVariable.fromJson(element);
break;
case 'field_dropdown':
field = Blockly.FieldDropdown.fromJson(element);
break;
case 'field_image':
field = Blockly.FieldImage.fromJson(element);
break;
case 'field_number':
field = Blockly.FieldNumber.fromJson(element);
break;
case 'field_date':
if (Blockly.FieldDate) {
field = Blockly.FieldDate.fromJson(element);
break;
}
// Fall through if FieldDate is not compiled in.
default:
field = Blockly.Field.fromJson(element);
// Unknown field.
if (element['alt']) {
element = element['alt'];
altRepeat = true;
if (!field) {
if (element['alt']) {
element = element['alt'];
altRepeat = true;
} else {
console.warn('Blockly could not create a field of type ' +
element['type'] +
'. You may need to register your custom field. See ' +
'github.com/google/blockly/issues/1584');
}
}
}
}

View File

@@ -52,6 +52,52 @@ Blockly.Field = function(text, opt_validator) {
this.setValidator(opt_validator);
};
/**
* The set of all registered fields, keyed by field type as used in the JSON
* definition of a block.
* @type {!Object<string, !{fromJson: Function}>}
* @private
*/
Blockly.Field.TYPE_MAP_ = {};
/**
* Registers a field type. May also override an existing field type.
* Blockly.Field.fromJson uses this registry to find the appropriate field.
* @param {!string} type The field type name as used in the JSON definition.
* @param {!{fromJson: Function}} fieldClass The field class containing a
* fromJson function that can construct an instance of the field.
* @throws {Error} if the type name is empty, or the fieldClass is not an
* object containing a fromJson function.
*/
Blockly.Field.register = function(type, fieldClass) {
if (!goog.isString(type) || goog.string.isEmptyOrWhitespace(type)) {
throw new Error('Invalid field type "' + type + '"');
}
if (!goog.isObject(fieldClass) || !goog.isFunction(fieldClass.fromJson)) {
throw new Error('Field "' + fieldClass +
'" must have a fromJson function');
}
Blockly.Field.TYPE_MAP_[type] = fieldClass;
};
/**
* Construct a Field from a JSON arg object.
* Finds the appropriate registered field by the type name as registered using
* Blockly.Field.register.
* @param {!Object} options A JSON object with a type and options specific
* to the field type.
* @returns {?Blockly.Field} The new field instance or null if a field wasn't
* found with the given type name
* @package
*/
Blockly.Field.fromJson = function(options) {
var fieldClass = Blockly.Field.TYPE_MAP_[options['type']];
if (fieldClass) {
return fieldClass.fromJson(options);
}
return null;
};
/**
* Temporary cache of text widths.
* @type {Object}

View File

@@ -327,3 +327,5 @@ Blockly.FieldAngle.prototype.classValidator = function(text) {
}
return String(n);
};
Blockly.Field.register('field_angle', Blockly.FieldAngle);

View File

@@ -127,3 +127,5 @@ Blockly.FieldCheckbox.prototype.showEditor_ = function() {
this.setValue(String(newState).toUpperCase());
}
};
Blockly.Field.register('field_checkbox', Blockly.FieldCheckbox);

View File

@@ -234,3 +234,5 @@ Blockly.FieldColour.widgetDispose_ = function() {
}
Blockly.Events.setGroup(false);
};
Blockly.Field.register('field_colour', Blockly.FieldColour);

View File

@@ -347,3 +347,5 @@ Blockly.FieldDate.CSS = [
' color: #fff;',
'}'
];
Blockly.Field.register('field_date', Blockly.FieldDate);

View File

@@ -563,3 +563,5 @@ Blockly.FieldDropdown.prototype.dispose = function() {
Blockly.WidgetDiv.hideIfOwner(this);
Blockly.FieldDropdown.superClass_.dispose.call(this);
};
Blockly.Field.register('field_dropdown', Blockly.FieldDropdown);

View File

@@ -216,3 +216,5 @@ Blockly.FieldImage.prototype.showEditor_ = function() {
this.clickHandler_(this);
}
};
Blockly.Field.register('field_image', Blockly.FieldImage);

View File

@@ -114,3 +114,5 @@ Blockly.FieldLabel.prototype.getSvgRoot = function() {
Blockly.FieldLabel.prototype.setTooltip = function(newTip) {
this.textElement_.tooltip = newTip;
};
Blockly.Field.register('field_label', Blockly.FieldLabel);

View File

@@ -114,3 +114,5 @@ Blockly.FieldNumber.prototype.classValidator = function(text) {
n = goog.math.clamp(n, this.min_, this.max_);
return String(n);
};
Blockly.Field.register('field_number', Blockly.FieldNumber);

View File

@@ -421,3 +421,5 @@ Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) {
}
return n;
};
Blockly.Field.register('field_input', Blockly.FieldTextInput);

View File

@@ -352,3 +352,5 @@ Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
}
this.setValue(id);
};
Blockly.Field.register('field_variable', Blockly.FieldVariable);

View File

@@ -97,3 +97,30 @@ function test_field_isEditable_nonEditableBlock_false() {
assertFalse('Non-editable field with non-editable block is not editable',
field.isCurrentlyEditable());
}
function test_field_register_with_custom_field() {
var CustomFieldType = function(value) {
CustomFieldType.superClass_.constructor.call(this, value);
};
goog.inherits(CustomFieldType, Blockly.Field);
CustomFieldType.fromJson = function(options) {
return new CustomFieldType(options['value']);
};
var json = {
type: 'field_custom_test',
value: 'ok'
};
// before registering
var field = Blockly.Field.fromJson(json);
assertNull(field);
Blockly.Field.register('field_custom_test', CustomFieldType);
// after registering
field = Blockly.Field.fromJson(json);
assertNotNull(field);
assertEquals(field.getValue(), 'ok');
}