diff --git a/core/block.js b/core/block.js index bec24df49..53b7ba1b1 100644 --- a/core/block.js +++ b/core/block.js @@ -46,11 +46,16 @@ goog.require('goog.string'); * @param {!Blockly.Workspace} workspace The block's workspace. * @param {?string} prototypeName Name of the language object containing * type-specific functions for this block. + * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise + * create a new id. * @constructor */ -Blockly.Block = function(workspace, prototypeName) { +Blockly.Block = function(workspace, prototypeName, opt_id) { /** @type {string} */ - this.id = Blockly.genUid(); + this.id = opt_id || Blockly.genUid(); + goog.asserts.assert(!Blockly.Block.getById(this.id), + 'Error: Block "%s" already exists.', this.id); + Blockly.Block.BlockDB_[this.id] = this; /** @type {Blockly.Connection} */ this.outputConnection = null; /** @type {Blockly.Connection} */ @@ -140,20 +145,6 @@ Blockly.Block.obtain = function(workspace, prototypeName) { */ Blockly.Block.prototype.data = null; -/** - * Get an existing block. - * @param {string} id The block's id. - * @param {!Blockly.Workspace} workspace The block's workspace. - * @return {Blockly.Block} The found block, or null if not found. - */ -Blockly.Block.getById = function(id, workspace) { - if (Blockly.Realtime.isEnabled()) { - return Blockly.Realtime.getBlockById(id); - } else { - return workspace.getBlockById(id); - } -}; - /** * Dispose of this block. * @param {boolean} healStack If true, then try to heal any gap by connecting @@ -201,10 +192,8 @@ Blockly.Block.prototype.dispose = function(healStack, animate, } connections[i].dispose(); } - // Remove from Realtime set of blocks. - if (Blockly.Realtime.isEnabled() && !Blockly.Realtime.withinSync) { - Blockly.Realtime.removeBlock(this); - } + // Remove from block database. + delete Blockly.Block.BlockDB_[this.id]; }; /** @@ -1240,3 +1229,19 @@ Blockly.Block.prototype.getRelativeToSurfaceXY = function() { Blockly.Block.prototype.moveBy = function(dx, dy) { this.xy_.translate(dx, dy); }; + +/** + * Database of all blocks. + * @private + */ +Blockly.Block.BlockDB_ = Object.create(null); + +/** + * Find the block with the specified ID. + * @param {string} id ID of block to find. + * @return {Blockly.Block} The sought after block or null if not found. + */ +Blockly.Block.getById = function(id) { + return Blockly.Block.BlockDB_[id] || null; +}; + diff --git a/core/block_svg.js b/core/block_svg.js index fa66ed4a2..15af148cc 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -40,11 +40,14 @@ goog.require('goog.math.Coordinate'); * @param {!Blockly.Workspace} workspace The block's workspace. * @param {?string} prototypeName Name of the language object containing * type-specific functions for this block. + * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise + * create a new id. * @extends {Blockly.Block} * @constructor */ -Blockly.BlockSvg = function(workspace, prototypeName) { - Blockly.BlockSvg.superClass_.constructor.call(this, workspace, prototypeName); +Blockly.BlockSvg = function(workspace, prototypeName, opt_id) { + Blockly.BlockSvg.superClass_.constructor.call(this, + workspace, prototypeName, opt_id); // Create core elements for the block. /** @type {SVGElement} */ this.svgGroup_ = Blockly.createSvgElement('g', {}, null); diff --git a/core/generator.js b/core/generator.js index 664a037b1..082101b40 100644 --- a/core/generator.js +++ b/core/generator.js @@ -202,6 +202,8 @@ Blockly.Generator.prototype.valueToCode = function(block, name, order) { } // Value blocks must return code and order of operations info. // Statement blocks must only return code. + goog.asserts.assertArray(tuple, 'Expecting tuple from value block "%s".', + targetBlock.type); var code = tuple[0]; var innerOrder = tuple[1]; if (isNaN(innerOrder)) { @@ -237,6 +239,7 @@ Blockly.Generator.prototype.statementToCode = function(block, name) { var code = this.blockToCode(targetBlock); // Value blocks must return code and order of operations info. // Statement blocks must only return code. + goog.asserts.assertString(code, 'Expecting code from statement block "%s".', targetBlock && targetBlock.type); if (code) { code = this.prefixLines(/** @type {string} */ (code), this.INDENT); diff --git a/core/workspace.js b/core/workspace.js index 84874d9f3..ac62f3fd2 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -152,10 +152,12 @@ Blockly.Workspace.prototype.getWidth = function() { * Obtain a newly created block. * @param {?string} prototypeName Name of the language object containing * type-specific functions for this block. + * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise + * create a new id. * @return {!Blockly.Block} The created block. */ -Blockly.Workspace.prototype.newBlock = function(prototypeName) { - return new Blockly.Block(this, prototypeName); +Blockly.Workspace.prototype.newBlock = function(prototypeName, opt_id) { + return new Blockly.Block(this, prototypeName, opt_id); }; /** diff --git a/core/workspace_svg.js b/core/workspace_svg.js index e5d23afd2..73f2ada76 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -250,10 +250,12 @@ Blockly.WorkspaceSvg.prototype.dispose = function() { * Obtain a newly created block. * @param {?string} prototypeName Name of the language object containing * type-specific functions for this block. + * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise + * create a new id. * @return {!Blockly.BlockSvg} The created block. */ -Blockly.WorkspaceSvg.prototype.newBlock = function(prototypeName) { - return new Blockly.BlockSvg(this, prototypeName); +Blockly.WorkspaceSvg.prototype.newBlock = function(prototypeName, opt_id) { + return new Blockly.BlockSvg(this, prototypeName, opt_id); }; /** @@ -445,7 +447,7 @@ Blockly.WorkspaceSvg.prototype.highlightBlock = function(id) { } var block = null; if (id) { - block = this.getBlockById(id); + block = Blockly.Block.getById(id); if (!block) { return; } diff --git a/core/xml.js b/core/xml.js index 8f54b1b5b..db9e0e497 100644 --- a/core/xml.js +++ b/core/xml.js @@ -295,14 +295,11 @@ Blockly.Xml.domToWorkspace = function(workspace, xml) { * workspace. * @param {!Blockly.Workspace} workspace The workspace. * @param {!Element} xmlBlock XML block element. - * @param {boolean=} opt_reuseBlock Optional arg indicating whether to - * reinitialize an existing block. * @return {!Blockly.Block} The root block created. */ -Blockly.Xml.domToBlock = function(workspace, xmlBlock, opt_reuseBlock) { +Blockly.Xml.domToBlock = function(workspace, xmlBlock) { // Create top-level block. - var topBlock = Blockly.Xml.domToBlockHeadless_(workspace, xmlBlock, - opt_reuseBlock); + var topBlock = Blockly.Xml.domToBlockHeadless_(workspace, xmlBlock); if (workspace.rendered) { // Hide connections to speed up assembly. topBlock.setConnectionsHidden(true); @@ -334,37 +331,17 @@ Blockly.Xml.domToBlock = function(workspace, xmlBlock, opt_reuseBlock) { * workspace. * @param {!Blockly.Workspace} workspace The workspace. * @param {!Element} xmlBlock XML block element. - * @param {boolean=} opt_reuseBlock Optional arg indicating whether to - * reinitialize an existing block. * @return {!Blockly.Block} The root block created. * @private */ -Blockly.Xml.domToBlockHeadless_ = - function(workspace, xmlBlock, opt_reuseBlock) { +Blockly.Xml.domToBlockHeadless_ = function(workspace, xmlBlock) { var block = null; var prototypeName = xmlBlock.getAttribute('type'); if (!prototypeName) { throw 'Block type unspecified: \n' + xmlBlock.outerHTML; } var id = xmlBlock.getAttribute('id'); - if (opt_reuseBlock && id) { - // Only used by realtime. - block = Blockly.Block.getById(id, workspace); - // TODO: The following is for debugging. It should never actually happen. - if (!block) { - throw 'Couldn\'t get Block with id: ' + id; - } - var parentBlock = block.getParent(); - // If we've already filled this block then we will dispose of it and then - // re-fill it. - if (block.workspace) { - block.dispose(true, false, true); - } - block.fill(workspace, prototypeName); - block.parent_ = parentBlock; - } else { - block = workspace.newBlock(prototypeName); - } + block = workspace.newBlock(prototypeName, id); var blockChild = null; for (var i = 0, xmlChild; xmlChild = xmlBlock.childNodes[i]; i++) { @@ -453,7 +430,7 @@ Blockly.Xml.domToBlockHeadless_ = } if (childBlockNode) { blockChild = Blockly.Xml.domToBlockHeadless_(workspace, - childBlockNode, opt_reuseBlock); + childBlockNode); if (blockChild.outputConnection) { input.connection.connect(blockChild.outputConnection); } else if (blockChild.previousConnection) { @@ -475,7 +452,7 @@ Blockly.Xml.domToBlockHeadless_ = throw 'Next statement is already connected.'; } blockChild = Blockly.Xml.domToBlockHeadless_(workspace, - childBlockNode, opt_reuseBlock); + childBlockNode); if (!blockChild.previousConnection) { throw 'Next block does not have previous statement.'; } diff --git a/tests/jsunit/block_test.js b/tests/jsunit/block_test.js new file mode 100644 index 000000000..a799340bc --- /dev/null +++ b/tests/jsunit/block_test.js @@ -0,0 +1,35 @@ +/** + * @license + * Blockly Tests + * + * Copyright 2015 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. + */ +'use strict'; + +function test_getById() { + var workspace = new Blockly.Workspace(); + var blockA = workspace.newBlock(''); + var blockB = workspace.newBlock(''); + assertEquals('Find blockA.', blockA, Blockly.Block.getById(blockA.id)); + assertEquals('Find blockB.', blockB, Blockly.Block.getById(blockB.id)); + assertEquals('No block found.', null, + Blockly.Block.getById('I do not exist.')); + blockA.dispose(); + assertEquals('Can\'t find blockA.', null, Blockly.Block.getById(blockA.id)); + assertEquals('BlockB exists.', blockB, Blockly.Block.getById(blockB.id)); + workspace.clear(); + assertEquals('Can\'t find blockB.', null, Blockly.Block.getById(blockB.id)); +} diff --git a/tests/jsunit/index.html b/tests/jsunit/index.html index f2e48cbfc..9c3df0ea6 100644 --- a/tests/jsunit/index.html +++ b/tests/jsunit/index.html @@ -8,6 +8,7 @@
+ diff --git a/tests/jsunit/workspace_test.js b/tests/jsunit/workspace_test.js index f4237a8a2..cf18f162e 100644 --- a/tests/jsunit/workspace_test.js +++ b/tests/jsunit/workspace_test.js @@ -66,17 +66,3 @@ function test_maxBlocksWorkspace() { workspace.clear(); assertEquals('Cleared capacity.', 0, workspace.remainingCapacity()); } - -function test_getByIdWorkspace() { - var workspace = new Blockly.Workspace(); - var blockA = workspace.newBlock(''); - var blockB = workspace.newBlock(''); - assertEquals('Find blockA.', blockA, workspace.getBlockById(blockA.id)); - assertEquals('Find blockB.', blockB, workspace.getBlockById(blockB.id)); - assertEquals('No block found.', null, workspace.getBlockById('I do not exist.')); - blockA.dispose(); - assertEquals('Can\'t find blockA.', null, workspace.getBlockById(blockA.id)); - assertEquals('BlockB exists.', blockB, workspace.getBlockById(blockB.id)); - workspace.clear(); - assertEquals('Can\'t find blockB.', null, workspace.getBlockById(blockB.id)); -}