mirror of
https://github.com/google/blockly.git
synced 2026-01-07 00:50:27 +01:00
Adds Block.prototype.mixin() and Blockly.Extensions.registerMixin(). (#907)
Adds Block.prototype.mixin() and Blockly.Extensions.registerMixin(). This adds support for a common use pattern in extensions, and adds error checking to avoid future incompatibilities.
This commit is contained in:
committed by
GitHub
parent
ec878b02cd
commit
15827c5d30
@@ -467,7 +467,7 @@ Blockly.Extensions.register("math_is_divisibleby_mutator",
|
||||
* @this {Blockly.Block}
|
||||
*/
|
||||
function() {
|
||||
goog.mixin(this, Blockly.Blocks.math.IS_DIVISIBLEBY_MUTATOR_MIXIN_);
|
||||
this.mixin(Blockly.Blocks.math.IS_DIVISIBLEBY_MUTATOR_MIXIN_);
|
||||
this.getField('PROPERTY').setValidator(function(option) {
|
||||
var divisorInput = (option == 'DIVISIBLE_BY');
|
||||
this.sourceBlock_.updateShape_(divisorInput);
|
||||
@@ -526,9 +526,8 @@ Blockly.Extensions.register("math_modes_of_list_mutator",
|
||||
* @this {Blockly.Block}
|
||||
*/
|
||||
function() {
|
||||
var thisBlock = this;
|
||||
goog.mixin(this, Blockly.Blocks.math.LIST_MODES_MUTATOR_MIXIN_);
|
||||
this.mixin(Blockly.Blocks.math.LIST_MODES_MUTATOR_MIXIN_);
|
||||
this.getField('OP').setValidator(function(newOp) {
|
||||
thisBlock.updateType_(newOp);
|
||||
});
|
||||
this.updateType_(newOp);
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
@@ -1042,6 +1042,34 @@ Blockly.Block.prototype.jsonInit = function(json) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add key/values from mixinObj to this block object. By default, this method
|
||||
* will check that the keys in mixinObj will not overwrite existing values in
|
||||
* the block, including prototype values. This provides some insurance against
|
||||
* mixin / extension incompatibilities with future block features. This check
|
||||
* can be disabled by passing true as the second argument.
|
||||
* @param {!Object} mixinObj The key/values pairs to add to this block object.
|
||||
* @param {boolean=} opt_disableCheck Option flag to disable overwrite checks.
|
||||
*/
|
||||
Blockly.Block.prototype.mixin = function(mixinObj, opt_disableCheck) {
|
||||
if (goog.isDef(opt_disableCheck) && !goog.isBoolean(opt_disableCheck)) {
|
||||
throw new Error("opt_disableCheck must be a boolean if provided");
|
||||
}
|
||||
if (!opt_disableCheck) {
|
||||
var overwrites = [];
|
||||
for (var key in mixinObj) {
|
||||
if (this[key] !== undefined) {
|
||||
overwrites.push(key);
|
||||
}
|
||||
}
|
||||
if (overwrites.length) {
|
||||
throw new Error('Mixin will overwrite block members: ' +
|
||||
JSON.stringify(overwrites));
|
||||
}
|
||||
}
|
||||
goog.mixin(this, mixinObj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Interpolate a message description onto the block.
|
||||
* @param {string} message Text contains interpolation tokens (%1, %2, ...)
|
||||
|
||||
@@ -63,6 +63,19 @@ Blockly.Extensions.register = function(name, initFn) {
|
||||
Blockly.Extensions.ALL_[name] = initFn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a new extension function that adds all key/value of mixinObj.
|
||||
* @param {string} name The name of this extension.
|
||||
* @param {!Object} mixinObj The values to mix in.
|
||||
* @throws {Error} if the extension name is empty or the extension is already
|
||||
* registered.
|
||||
*/
|
||||
Blockly.Extensions.registerMixin = function(name, mixinObj) {
|
||||
Blockly.Extensions.register(name, function() {
|
||||
this.mixin(mixinObj);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies an extension method to a block. This should only be called during
|
||||
* block construction.
|
||||
|
||||
@@ -193,4 +193,114 @@ function test_parent_tooltip_when_inline() {
|
||||
delete Blockly.Blocks['test_parent_tooltip_when_inline'];
|
||||
delete Blockly.Blocks['test_parent'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function test_mixin_extension() {
|
||||
var TEST_MIXIN = {
|
||||
field: 'FIELD',
|
||||
method: function() {
|
||||
console.log('TEXT_MIXIN method()');
|
||||
}
|
||||
};
|
||||
|
||||
var workspace = new Blockly.Workspace();
|
||||
var block;
|
||||
try {
|
||||
assertUndefined(Blockly.Extensions.ALL_['mixin_test']);
|
||||
|
||||
// Extension defined before the block type is defined.
|
||||
Blockly.Extensions.registerMixin('mixin_test', TEST_MIXIN);
|
||||
assert(goog.isFunction(Blockly.Extensions.ALL_['mixin_test']));
|
||||
|
||||
Blockly.defineBlocksWithJsonArray([{
|
||||
"type": "test_block_mixin",
|
||||
"message0": "test_block_mixin",
|
||||
"extensions": ["mixin_test"]
|
||||
}]);
|
||||
|
||||
block = new Blockly.Block(workspace, 'test_block_mixin');
|
||||
|
||||
assertEquals(TEST_MIXIN.field, block.field);
|
||||
assertEquals(TEST_MIXIN.method, block.method);
|
||||
} finally {
|
||||
block && block.dispose();
|
||||
workspace.dispose();
|
||||
|
||||
delete Blockly.Extensions.ALL_['mixin_test'];
|
||||
delete Blockly.Blocks['test_block_mixin'];
|
||||
}
|
||||
}
|
||||
|
||||
function test_bad_mixin_overwrites_local_value() {
|
||||
var TEST_MIXIN_BAD_INPUTLIST = {
|
||||
inputList: 'bad inputList' // Defined in constructor
|
||||
};
|
||||
|
||||
var workspace = new Blockly.Workspace();
|
||||
var block;
|
||||
try {
|
||||
assertUndefined(Blockly.Extensions.ALL_['mixin_bad_inputList']);
|
||||
|
||||
// Extension defined before the block type is defined.
|
||||
Blockly.Extensions.registerMixin('mixin_bad_inputList', TEST_MIXIN_BAD_INPUTLIST);
|
||||
assert(goog.isFunction(Blockly.Extensions.ALL_['mixin_bad_inputList']));
|
||||
|
||||
Blockly.defineBlocksWithJsonArray([{
|
||||
"type": "test_block_bad_inputList",
|
||||
"message0": "test_block_bad_inputList",
|
||||
"extensions": ["mixin_bad_inputList"]
|
||||
}]);
|
||||
|
||||
try {
|
||||
block = new Blockly.Block(workspace, 'test_block_bad_inputList');
|
||||
} catch (e) {
|
||||
// Expected Error
|
||||
assert(e.message.indexOf('inputList') >= 0); // Reference the conflict
|
||||
return;
|
||||
}
|
||||
fail('Expected error when constructing block');
|
||||
} finally {
|
||||
block && block.dispose();
|
||||
workspace.dispose();
|
||||
|
||||
delete Blockly.Extensions.ALL_['mixin_bad_inputList'];
|
||||
delete Blockly.Blocks['test_block_bad_inputList'];
|
||||
}
|
||||
}
|
||||
|
||||
function test_bad_mixin_overwrites_prototype() {
|
||||
var TEST_MIXIN_BAD_COLOUR = {
|
||||
colour_: 'bad colour_' // Defined on prototype
|
||||
};
|
||||
|
||||
var workspace = new Blockly.Workspace();
|
||||
var block;
|
||||
try {
|
||||
assertUndefined(Blockly.Extensions.ALL_['mixin_bad_colour_']);
|
||||
|
||||
// Extension defined before the block type is defined.
|
||||
Blockly.Extensions.registerMixin('mixin_bad_colour_', TEST_MIXIN_BAD_COLOUR);
|
||||
assert(goog.isFunction(Blockly.Extensions.ALL_['mixin_bad_colour_']));
|
||||
|
||||
Blockly.defineBlocksWithJsonArray([{
|
||||
"type": "test_block_bad_colour",
|
||||
"message0": "test_block_bad_colour",
|
||||
"extensions": ["mixin_bad_colour_"]
|
||||
}]);
|
||||
|
||||
try {
|
||||
block = new Blockly.Block(workspace, 'test_block_bad_colour');
|
||||
} catch (e) {
|
||||
// Expected Error
|
||||
assert(e.message.indexOf('colour_') >= 0); // Reference the conflict
|
||||
return;
|
||||
}
|
||||
fail('Expected error when constructing block');
|
||||
} finally {
|
||||
block && block.dispose();
|
||||
workspace.dispose();
|
||||
|
||||
delete Blockly.Extensions.ALL_['mixin_bad_colour_'];
|
||||
delete Blockly.Blocks['test_block_bad_colour'];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user