mirror of
https://github.com/google/blockly.git
synced 2026-05-12 23:20:10 +02:00
Add JSO hooks for blocks and fields. (#5052)
* Add JSON serialiation hooks for fields * Add checking for JSON hooks * Fix other checks and move checks to function * Remove error for both serialization hooks being defined * Fixup comments and errors * Add tests * Add json hooks to block properties * Cleanup * Rip out fragile backwards compatibility
This commit is contained in:
+18
-2
@@ -328,18 +328,34 @@ Blockly.Block.prototype.onchange;
|
||||
|
||||
/**
|
||||
* An optional serialization method for defining how to serialize the
|
||||
* mutation state. This must be coupled with defining `domToMutation`.
|
||||
* mutation state to XML. This must be coupled with defining `domToMutation`.
|
||||
* @type {?function(...):!Element}
|
||||
*/
|
||||
Blockly.Block.prototype.mutationToDom;
|
||||
|
||||
/**
|
||||
* An optional deserialization method for defining how to deserialize the
|
||||
* mutation state. This must be coupled with defining `mutationToDom`.
|
||||
* mutation state from XML. This must be coupled with defining `mutationToDom`.
|
||||
* @type {?function(!Element)}
|
||||
*/
|
||||
Blockly.Block.prototype.domToMutation;
|
||||
|
||||
/**
|
||||
* An optional serialization method for defining how to serialize the block's
|
||||
* extra state (eg mutation state) to something JSON compatible. This must be
|
||||
* coupled with defining `loadExtraState`.
|
||||
* @type {?function(): *}
|
||||
*/
|
||||
Blockly.Block.prototype.saveExtraState;
|
||||
|
||||
/**
|
||||
* An optional serialization method for defining how to deserialize the block's
|
||||
* extra state (eg mutation state) from something JSON compatible. This must be
|
||||
* coupled with defining `saveExtraState`.
|
||||
* @type {?function(*)}
|
||||
*/
|
||||
Blockly.Block.prototype.loadExtraState;
|
||||
|
||||
/**
|
||||
* An optional property for suppressing adding STATEMENT_PREFIX and
|
||||
* STATEMENT_SUFFIX to generated code.
|
||||
|
||||
+88
-42
@@ -86,17 +86,11 @@ Blockly.Extensions.registerMutator = function(name, mixinObj, opt_helperFn,
|
||||
opt_blockList) {
|
||||
var errorPrefix = 'Error when registering mutator "' + name + '": ';
|
||||
|
||||
// Sanity check the mixin object before registering it.
|
||||
Blockly.Extensions.checkHasFunction_(
|
||||
errorPrefix, mixinObj.domToMutation, 'domToMutation');
|
||||
Blockly.Extensions.checkHasFunction_(
|
||||
errorPrefix, mixinObj.mutationToDom, 'mutationToDom');
|
||||
|
||||
Blockly.Extensions.checkHasMutatorProperties_(errorPrefix, mixinObj);
|
||||
var hasMutatorDialog =
|
||||
Blockly.Extensions.checkMutatorDialog_(mixinObj, errorPrefix);
|
||||
|
||||
if (opt_helperFn && (typeof opt_helperFn != 'function')) {
|
||||
throw Error('Extension "' + name + '" is not a function');
|
||||
throw Error(errorPrefix + 'Extension "' + name + '" is not a function');
|
||||
}
|
||||
|
||||
// Sanity checks passed.
|
||||
@@ -154,7 +148,7 @@ Blockly.Extensions.apply = function(name, block, isMutator) {
|
||||
|
||||
if (isMutator) {
|
||||
var errorPrefix = 'Error after applying mutator "' + name + '": ';
|
||||
Blockly.Extensions.checkBlockHasMutatorProperties_(errorPrefix, block);
|
||||
Blockly.Extensions.checkHasMutatorProperties_(errorPrefix, block);
|
||||
} else {
|
||||
if (!Blockly.Extensions.mutatorPropertiesMatch_(
|
||||
/** @type {!Array<Object>} */ (mutatorProperties), block)) {
|
||||
@@ -203,54 +197,100 @@ Blockly.Extensions.checkNoMutatorProperties_ = function(mutationName, block) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that the given object has both or neither of the functions required
|
||||
* to have a mutator dialog.
|
||||
* These functions are 'compose' and 'decompose'. If a block has one, it must
|
||||
* have both.
|
||||
* Checks if the given object has both the 'mutationToDom' and 'domToMutation'
|
||||
* functions.
|
||||
* @param {!Object} object The object to check.
|
||||
* @param {string} errorPrefix The string to prepend to any error message.
|
||||
* @return {boolean} True if the object has both functions. False if it has
|
||||
* neither function.
|
||||
* @throws {Error} if the object has only one of the functions.
|
||||
* @throws {Error} if the object has only one of the functions, or either is
|
||||
* not actually a function.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.checkMutatorDialog_ = function(object, errorPrefix) {
|
||||
var hasCompose = object.compose !== undefined;
|
||||
var hasDecompose = object.decompose !== undefined;
|
||||
|
||||
if (hasCompose && hasDecompose) {
|
||||
if (typeof object.compose != 'function') {
|
||||
throw Error(errorPrefix + 'compose must be a function.');
|
||||
} else if (typeof object.decompose != 'function') {
|
||||
throw Error(errorPrefix + 'decompose must be a function.');
|
||||
}
|
||||
return true;
|
||||
} else if (!hasCompose && !hasDecompose) {
|
||||
return false;
|
||||
}
|
||||
throw Error(errorPrefix +
|
||||
'Must have both or neither of "compose" and "decompose"');
|
||||
Blockly.Extensions.checkXmlHooks_ = function(object, errorPrefix) {
|
||||
return Blockly.Extensions.checkHasFunctionPair_(
|
||||
object, 'mutationToDom', 'domToMutation', errorPrefix);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that a block has required mutator properties. This should be called
|
||||
* after applying a mutation extension.
|
||||
* Checks if the given object has both the 'saveExtraState' and 'loadExtraState'
|
||||
* functions.
|
||||
* @param {!Object} object The object to check.
|
||||
* @param {string} errorPrefix The string to prepend to any error message.
|
||||
* @param {!Blockly.Block} block The block to inspect.
|
||||
* @return {boolean} True if the object has both functions. False if it has
|
||||
* neither function.
|
||||
* @throws {Error} if the object has only one of the functions, or either is
|
||||
* not actually a function.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.checkBlockHasMutatorProperties_ = function(errorPrefix,
|
||||
block) {
|
||||
if (typeof block.domToMutation != 'function') {
|
||||
throw Error(errorPrefix + 'Applying a mutator didn\'t add "domToMutation"');
|
||||
}
|
||||
if (typeof block.mutationToDom != 'function') {
|
||||
throw Error(errorPrefix + 'Applying a mutator didn\'t add "mutationToDom"');
|
||||
}
|
||||
Blockly.Extensions.checkJsonHooks_ = function(object, errorPrefix) {
|
||||
return Blockly.Extensions.checkHasFunctionPair_(
|
||||
object, 'saveExtraState', 'loadExtraState', errorPrefix);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the given object has both the 'compose' and 'decompose' functions.
|
||||
* @param {!Object} object The object to check.
|
||||
* @param {string} errorPrefix The string to prepend to any error message.
|
||||
* @return {boolean} True if the object has both functions. False if it has
|
||||
* neither function.
|
||||
* @throws {Error} if the object has only one of the functions, or either is
|
||||
* not actually a function.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.checkMutatorDialog_ = function(object, errorPrefix) {
|
||||
return Blockly.Extensions
|
||||
.checkHasFunctionPair_(object, 'compose', 'decompose', errorPrefix);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks that the given object has both or neither of the given functions, and
|
||||
* that they are indeed functions.
|
||||
* @param {!Object} object The object to check.
|
||||
* @param {string} name1 The name of the first function in the pair.
|
||||
* @param {string} name2 The name of the second function in the pair.
|
||||
* @param {string} errorPrefix The string to prepend to any error message.
|
||||
* @return {boolean} True if the object has both functions. False if it has
|
||||
* neither function.
|
||||
* @throws {Error} If the object has only one of the functions, or either is
|
||||
* not actually a function.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.checkHasFunctionPair_ =
|
||||
function(object, name1, name2, errorPrefix) {
|
||||
var has1 = object[name1] !== undefined;
|
||||
var has2 = object[name2] !== undefined;
|
||||
|
||||
if (has1 && has2) {
|
||||
if (typeof object[name1] != 'function') {
|
||||
throw Error(errorPrefix + name1 + ' must be a function.');
|
||||
} else if (typeof object[name2] != 'function') {
|
||||
throw Error(errorPrefix + name2 + ' must be a function.');
|
||||
}
|
||||
return true;
|
||||
} else if (!has1 && !has2) {
|
||||
return false;
|
||||
}
|
||||
throw Error(errorPrefix +
|
||||
'Must have both or neither of "' + name1 + '" and "' + name2 + '"');
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks that the given object required mutator properties.
|
||||
* @param {string} errorPrefix The string to prepend to any error message.
|
||||
* @param {!Object} object The object to inspect.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.checkHasMutatorProperties_ = function(errorPrefix, object) {
|
||||
var hasXmlHooks = Blockly.Extensions.checkXmlHooks_(object, errorPrefix);
|
||||
var hasJsonHooks = Blockly.Extensions.checkJsonHooks_(object, errorPrefix);
|
||||
if (!hasXmlHooks && !hasJsonHooks) {
|
||||
throw Error(errorPrefix +
|
||||
'Mutations must contain either XML hooks, or JSON hooks, or both');
|
||||
}
|
||||
// A block with a mutator isn't required to have a mutation dialog, but
|
||||
// it should still have both or neither of compose and decompose.
|
||||
Blockly.Extensions.checkMutatorDialog_(block, errorPrefix);
|
||||
Blockly.Extensions.checkMutatorDialog_(object, errorPrefix);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -270,6 +310,12 @@ Blockly.Extensions.getMutatorProperties_ = function(block) {
|
||||
if (block.mutationToDom !== undefined) {
|
||||
result.push(block.mutationToDom);
|
||||
}
|
||||
if (block.saveExtraState !== undefined) {
|
||||
result.push(block.saveExtraState);
|
||||
}
|
||||
if (block.loadExtraState !== undefined) {
|
||||
result.push(block.loadExtraState);
|
||||
}
|
||||
if (block.compose !== undefined) {
|
||||
result.push(block.compose);
|
||||
}
|
||||
|
||||
@@ -415,6 +415,26 @@ Blockly.Field.prototype.toXml = function(fieldElement) {
|
||||
return fieldElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves this fields value as something which can be serialized to JSON. Should
|
||||
* only be called by the serialization system.
|
||||
* @return {*} JSON serializable state.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Field.prototype.saveState = function() {
|
||||
return this.getValue();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the field's state based on the given state value. Should only be called
|
||||
* by the serialization system.
|
||||
* @param {*} state The state we want to apply to the field.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Field.prototype.loadState = function(state) {
|
||||
this.setValue(state);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of all DOM objects and events belonging to this editable field.
|
||||
* @package
|
||||
|
||||
@@ -448,6 +448,57 @@ suite('Extensions', function() {
|
||||
}, /mutationToDom/);
|
||||
});
|
||||
|
||||
test('No saveExtraState', function() {
|
||||
this.extensionsCleanup_.push('mutator_test');
|
||||
chai.assert.throws(function() {
|
||||
Blockly.Extensions.registerMutator('mutator_test',
|
||||
{
|
||||
loadExtraState: function() {
|
||||
return 'loadExtraState';
|
||||
},
|
||||
compose: function() {
|
||||
return 'composeFn';
|
||||
},
|
||||
decompose: function() {
|
||||
return 'decomposeFn';
|
||||
}
|
||||
});
|
||||
}, /saveExtraState/);
|
||||
});
|
||||
|
||||
test('No loadExtraState', function() {
|
||||
this.extensionsCleanup_.push('mutator_test');
|
||||
chai.assert.throws(function() {
|
||||
Blockly.Extensions.registerMutator('mutator_test',
|
||||
{
|
||||
saveExtraState: function() {
|
||||
return 'saveExtraState';
|
||||
},
|
||||
compose: function() {
|
||||
return 'composeFn';
|
||||
},
|
||||
decompose: function() {
|
||||
return 'decomposeFn';
|
||||
}
|
||||
});
|
||||
}, /loadExtraState/);
|
||||
});
|
||||
|
||||
test('No serialization hooks', function() {
|
||||
this.extensionsCleanup_.push('mutator_test');
|
||||
chai.assert.throws(function() {
|
||||
Blockly.Extensions.registerMutator('mutator_test',
|
||||
{
|
||||
compose: function() {
|
||||
return 'composeFn';
|
||||
},
|
||||
decompose: function() {
|
||||
return 'decomposeFn';
|
||||
}
|
||||
});
|
||||
}, 'Mutations must contain either XML hooks, or JSON hooks, or both');
|
||||
});
|
||||
|
||||
test('Has decompose but no compose', function() {
|
||||
this.extensionsCleanup_.push('mutator_test');
|
||||
chai.assert.throws(function() {
|
||||
|
||||
Reference in New Issue
Block a user