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:
Andrew n marshall
2017-02-06 10:00:08 -08:00
committed by GitHub
parent ec878b02cd
commit 15827c5d30
4 changed files with 156 additions and 6 deletions

View File

@@ -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));
});

View File

@@ -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, ...)

View File

@@ -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.

View File

@@ -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'];
}
}