block.workspace.topBlocks is an empty array for all but the first block when attempting to delete a group of blocks

848 views
Skip to first unread message

erin.a....@gmail.com

unread,
Feb 17, 2018, 11:56:23 PM2/17/18
to Blockly
Help!  I've spend countless hours building a dynamic version of blockly (blocks are created based on response data from an http request to get API commands).  The XML is dynamically appended to the DOM with JavaScript and the block fields are populated based on API command parameters.  Everything works swimmingly EXCEPT, when you go to dispose of a group of blocks that have been dragged into the workspace, blockly barfs.  The reason seems to be that, when iterating through the blocks and nulling their workspaces blockly.prototype.dispose, blockly checks to make sure that the topBlock of the workspace of each block is the same as the block itself, but it finds that the topBlock is = [ ].  I don't have any idea why this is happening and it is a dealbreaker.  

Any help or knowledge about the root source of this malfunction would be greatly appreciated. 

Thank you,
Erin

Erin Connolly

unread,
Feb 19, 2018, 10:50:50 AM2/19/18
to Blockly
On Saturday, February 17, 2018 at 9:56:23 PM UTC-7, erin.a....@gmail.com wrote:
Help!  I've spend countless hours building a dynamic version of blockly (blocks are created based on response data from an http request to get API commands).  The XML is dynamically appended to the DOM with JavaScript and the block fields are populated based on API command parameters.  Everything works swimmingly EXCEPT, when you go to dispose of a group of blocks that have been dragged into the workspace, blockly barfs.  The reason seems to be that, when iterating through the blocks and nulling their workspaces blockly.prototype.dispose, blockly checks to make sure that the topBlock of the workspace of each block is the same as the block itself, but it finds that the topBlock is = [ ].  I don't have any idea why this is happening and it is a dealbreaker.  

Here, in general, is the code that appends the blocks to the xml and adds the blocks to Blockly.Blocks:

function getCommandNames(result) {
	if (result !== "") {
		let postCommands = [], getCommands = [];
		let theDataObject = JSON.parse(result);
 
		$.each(theDataObject.Post, function (i, com) {
			if ($.inArray(com, postCommands) === -1) { postCommands.push(com) }
		});
		$.each(theDataObject.Get, function (i, com) {
			if ($.inArray(com, getCommands) === -1) { getCommands.push(com) }
		});
 
		let allCommandsOnRobot = postCommands.concat(getCommands);
		let postCommandInfo = [], getCommandInfo = [], commands = [];
		for (let index of allCommandsOnRobot) {
			!notYetImplemented.includes(index) ? commands.push(index) : null;
		}
		getArguments(commands, postCommandInfo, getCommandInfo);
	}
}
 
 
function getArguments(commands, postCommandInfo, getCommandInfo) {
	if (commands.length) {
		let endpoint = "info/help?command=" + commands[0];
		sendGetRequestToRobot(endpoint, ip, function (result) {
			let theDataObject = JSON.parse(result);
			if (theDataObject.Post) { postCommandInfo.push(theDataObject.Post[0]) };
			if (theDataObject.Get) { getCommandInfo.push(theDataObject.Get[0]) };
			commands.shift();
			getArguments(commands, postCommandInfo, getCommandInfo);
		});
	} else {
		constructCategories(postCommandInfo, getCommandInfo);
	}
};
 
//#generate categories (aka tabs) from robot

function constructCategories(postCommandInfo, getCommandInfo) {
	var commandGroupOfEachPostCommand = postCommandInfo.map(x => x.ApiCommand.ApiCommandGroup);
	var commandGroupOfEachGetCommand = getCommandInfo.map(x => x.ApiCommand.ApiCommandGroup)
	const postCommandGroups = {}; const getCommandGroups = {};
	for (const key of commandGroupOfEachPostCommand) {
		!postCommandGroups[key] ? postCommandGroups[key] = new Array : null;
		!getCommandGroups[key] ? getCommandGroups[key] = new Array : null;
	};
	var categoryArray = Object.keys(postCommandGroups);
 
	for (var i = 0; i < categoryArray.length; i++) {
		for (var j = 0; j < postCommandInfo.length; j++) {
			postCommandInfo[j].ApiCommand.ApiCommandGroup === categoryArray[i] ? postCommandGroups[categoryArray[i]].push(postCommandInfo[j]) : null
		};
		for (var n = 0; n < getCommandInfo.length; n++) {
			getCommandInfo[n].ApiCommand.ApiCommandGroup === categoryArray[i] ? getCommandGroups[categoryArray[i]].push(getCommandInfo[n]) : null
		};
	};
	constructCommandObjects(getCommandGroups, postCommandGroups, categoryArray);
}
 
function constructCommandObjects(getCommandGroups, postCommandGroups, categories) {
	let postCommandObjects = []; getCommandObjects = []
	for (var k = 0; k < categories.length; k++) {
		let completePostCategory = [];
		let completeGetCategory = [];
		let postGroup = postCommandGroups[categories[k]]
		let getGroup = getCommandGroups[categories[k]]
		for (var x = 0; x < postGroup.length; x++) {
			let command = {
				"Name": postGroup[x].ApiCommand.Name,
				"Endpoint": postGroup[x].Endpoint,
				"Arguments": postGroup[x].ApiCommand.Arguments,
			}
			completePostCategory.push(command);
		}
		for (var z = 0; z < getGroup.length; z++) {
			let command = {
				"Name": getGroup[z].ApiCommand.Name,
				"Endpoint": getGroup[z].Endpoint,
				"Arguments": getGroup[z].ApiCommand.Arguments,
			}
			completeGetCategory.push(command);
		}
		if (categories[k] === "Display") {
			completePostCategory.push({ "Name""BrowseToImageFile""Arguments"null })
		}
		if (categories[k] === "Speakers") {
			completePostCategory.push({ "Name""BrowseToAudioFile""Arguments"null })
		}
		postCommandObjects.push(completePostCategory);
		getCommandObjects.push(completeGetCategory);
	}
	updateMyBlocks(categories, postCommandObjects, getCommandObjects);
}
 
 
 
function updateMyBlocks(categories, postCommandObjects, getCommandObjects) {
	var colours = ["#745CA6""#FBBD0B""#D11149""#4285F4""#745CA6""#E6C229""#F17105""#1A8FE3""#6B09EE"];
	var blocklyColours = [260463442182604828206266];
 
	//start appending category tags to #toolbox
	for (var y = 0; y < categories.length; y++) {
		let getBlocks = getCommandObjects[y];
		let postBlocks = postCommandObjects[y];
		var MyBlocks = document.createElement("category");
		My.setAttribute("name", categories[y]);
		My.setAttribute("colour", colours[y]);
		for (var i = 0; i < getBlocks.length; i++) {
			addBlock(getBlocks[i], MyBlocks, blocklyColours[y], "GET");
		}
		for (var m = 0; m < postBlocks.length; m++) {
			addBlock(postBlocks[m], MyBlocks, blocklyColours[y], "POST");
		}
		toolbox.appendChild(MyBlocks);
	}
	workspace.updateToolbox(toolbox);
}
 
//#Add blocks and tabs to UI

function addBlock(block, MyBlocks, colour, httpVerb) {
	var blockName = block.Name.replace("Api.""");
	var endpoint = block.Endpoint;
	var legacyCommands = Object.keys(hardCodedCommands);
	var commandArguments = block.Arguments;
	var argsAsStrings = commandArguments ? Object.keys(commandArguments) : "";
	var parameters = Array.from(argsAsStrings, x => commandArguments[x]);
	var xmlFields = [];
	var myBlock = document.createElement("block");
	myBlock.setAttribute("type", blockName);
	myBlock.setAttribute("disabled"false);
	My.appendChild(myBlock);
 
	if (!arrayContains(legacyCommands, blockName)) {
		Blockly.Blocks[blockName] = {
			init: function () {
				this.setColour(colour);
				var dummy = this.appendDummyInput();
				dummy.appendField(blockName);
				for (var k = 0; k < argsAsStrings.length; k++) {
					var argDetails = parameters[k];
					dummy.appendField(argDetails.Name);
					let fieldValue = "FIELD_" + blockName + "_" + argsAsStrings[k];
					let typeInfo = argDetails.GetValueType;
					let type = typeInfo.substring(7, typeInfo.indexOf(","));
					let fieldTypes = blocklyTypes(argDetails.Value, -100100);
					let fieldType = fieldTypes[type];
					dummy.appendField(fieldType, fieldValue);
					addFieldsToBlock(myBlock, xmlFields, fieldValue);
				}
				this.setPreviousStatement(truenull);
				this.setNextStatement(truenull);
				this.setOutput(true);
				this.setTooltip(blockName);
			}
		}
 
 
		Blockly.JavaScript[blockName] = function (block) {
			const payload = {};
			var httpRequest;
			for (const arg of argsAsStrings) {
				var input = parseInt(block.getFieldValue("FIELD_" + blockName + "_" + arg));
				payload[arg] = input;
			};
			var code;
			console.log(payload);
			if (httpVerb === "GET") {
				code = "sendGetRequestToRobot(\"" + endpoint + "\",\"" + ip + "\");";
			} else {
				code = "sendPostRequestToRobot(\"" + endpoint + "\",\"" + ip + "\"," + JSON.stringify(payload) + ");";
			}
 
			return code;
		};
	} else {
		legacyBlocks(block, My, blockName, myBlock, xmlFields, argsAsStrings, colour, endpoint);
	} 
}; 

Andrew n marshall

unread,
Feb 20, 2018, 12:31:32 PM2/20/18
to blo...@googlegroups.com
The XML is dynamically appended to the DOM with JavaScript and the block fields are populated based on API command parameters

Which DOM are you referring to?  I don't see any call to Blockly.Xml.domToWorkspace(..) in the code above.

Your addBlock(..) look good (at a glance), but will only get as far as registering the block and its generator.  To be useful, you will need to create a blockName block either in the toolbox (via a constructed XML; possibly via a dynamic toolbox category) or add it to the workspace (via Workspace.newBlock(..)).

--
You received this message because you are subscribed to the Google Groups "Blockly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Erin Connolly

unread,
Feb 20, 2018, 12:50:31 PM2/20/18
to Blockly
Adam, 

Sorry I did not include the portion of the code that identifies the toolbox in relation to the DOM.  In my Index.html page I have xml tags with id="toolbox".
var toolbox = document.getElementById('toolbox')
var workspace = Blockly.inject(BlocklyDiv, options).  
var BlocklyDiv = document.getElementById('blocklyDiv')
var options = {toolbox: toolbox, collapse: true, etc}

Within the updateMyBlocks and addBlock functions, the categories and blocks are created and appended to the toolbox.  I actually did not find out about blockly's helper function, Blockly.Xml.domToWorkspace(..), until last weekend, a few weeks after I started working on this.  I am not sure how it works yet.  But I may try it.  

Thank you,
Erin 


On Saturday, February 17, 2018 at 9:56:23 PM UTC-7, erin.a....@gmail.com wrote:

Andrew n marshall

unread,
Feb 21, 2018, 8:44:34 PM2/21/18
to blo...@googlegroups.com
var toolbox = document.getElementById('toolbox')
var workspace = Blockly.inject(BlocklyDiv, options).  
var BlocklyDiv = document.getElementById('blocklyDiv')
var options = {toolbox: toolbox, collapse: true, etc}

This code assigns toolbox to the toolbox property after you've already called inject(..). What happens when you construct options immediately after getting the toolbox element, and before calling inject()?

Blockly.Xml.domToWorkspace(..) is the primary and recommended way to load blocks into the workspace.


--

Erin Connolly

unread,
Feb 26, 2018, 6:20:56 PM2/26/18
to Blockly
Andrew,
That is not true.  options is initialized as Blockly.inject is called.  Hoisting takes care of this. 
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+u...@googlegroups.com.

a...@anm.me

unread,
Mar 7, 2018, 4:41:28 PM3/7/18
to Blockly
(Sorry for not getting back in a timely manner.)

It seems you misunderstand hoisting. Hoisting makes the variable available, but the assignment operator is still executed in sequence, on the fourth line of the example above.

Consider the following code:

console.log('#1', options);
var toolbox = "I'm a toolbox"
console.log('#2', options);
var options = {toolbox: toolbox, collapse: true}
console.log('#3', options);

This outputs:

#1 undefined
#2 undefined
#3 {toolbox: "I'm a toolbox", collapse: true}

Even through both #1 and #2 reference options, the variable is not assigned until after 2. The assignment has to be before the call that uses the variable.

erin.a....@gmail.com

unread,
Mar 9, 2018, 12:47:44 PM3/9/18
to Blockly
ah yes, you are right.  Thank you.  I have now moved the toolbox and other DOM selectors inside a document ready function, and the var options is assigned before 
var workspace.  However, I'm still seeing the same problem. I think what I will need to do is make sure that my xml is appended before workspace.UpdateToolbox.

I n

erin.a....@gmail.com

unread,
Mar 9, 2018, 1:37:39 PM3/9/18
to Blockly
It is really the updateToolbox function that seems to be creating a problem.

Andrew n marshall

unread,
Mar 9, 2018, 5:49:17 PM3/9/18
to blo...@googlegroups.com

I tested running Blockly.workspace.updateToolbox(toolboxXmlElement) in our test code, followed by creating and deleting several blocks. All of this worked exactly as expected.

Do you have a pointer to a runnable version of your project?
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+unsubscribe@googlegroups.com.

Erin Connolly

unread,
Mar 9, 2018, 6:45:43 PM3/9/18
to Blockly
Andrew, 

I am not unfortunately able to share the project.  I can say that, when I check the index.html page before I call Blockly.workspace.updateToolbox(toolbox), the toolbox div has been updated to include new categories, blocks, and fields.  The result of updateToolbox is to make all those categories appear in the UI....but when I go to delete more than one block at a time, I get "Uncaught Block not present in workspace's list of top-most blocks."

Do you know anything about what causes that error?  I drilled down a bit and found that, after the first block is deleted and it's workspace made null, the  second block's workspace's top-most block is an empty array. When the blockly function, removeTopBlock, looks for the block itself in that array, it does not find anything.  Any notion of why this would happen?

Andrew n marshall

unread,
Mar 9, 2018, 7:36:28 PM3/9/18
to blo...@googlegroups.com
I get "Uncaught Block not present in workspace's list of top-most blocks." Do you know anything about what causes that error?

I don't believe I've not seen that error before.
 
 
when I go to delete more than one block at a time...

By what means? I used the workspace context menu to delete all blocks.

If you're doing things programmatically, remember that a block dragged from the toolbox is a copy, not identical, to the one in the toolbox.
 
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+unsubscribe@googlegroups.com.

Andrew n marshall

unread,
Mar 9, 2018, 7:54:17 PM3/9/18
to blo...@googlegroups.com
[Resent with edits] 
 
I get "Uncaught Block not present in workspace's list of top-most blocks." Do you know anything about what causes that error?

I don't believe I've seen that error before, but the error seems self explanatory.



when I go to delete more than one block at a time...

By what means? I used the workspace context menu to delete all blocks.

If you're doing things programmatically, remember that a block dragged from the toolbox is a copy, not identical, to the one in the toolbox.

Another far off possibility (I don't know how you'd get into this state): Make sure you haven't added a block to top blocks that might be a child of another block. This would explain the behavior you're describing... Deletion clears out all of its children before it proceeding to the next top block. If that next top block was somehow a child of a first block (somehow), it would have already been cleared, I think.

Message has been deleted

Erin Connolly

unread,
Mar 13, 2018, 3:59:47 PM3/13/18
to Blockly
Andrew, 

I'm thinking this is a scope issue, but I cannot tell what I am missing...as in I need to register these blocks on the workspace somehow?  I am attaching another simplified version of my code below:

<div id="blocklyArea" class="container-fluid" style="height:95%; position: relative;">
     <div id="blocklyDiv" class="col-xs-12" style="height:100%;"></div>
</div>
<xml id="toolbox" style="display:none;">
<category name="Blockly Blocks" colour="#4285F4">
<block type="controls_if"></block>
<block type="logic_compare">
<field name="OP">EQ</field>
</block>....
                        <block type="colour_blend">
  <value name="COLOUR1">
<shadow type="colour_picker">
     <field name="COLOUR">#ff0000</field>
</shadow>
  </value>
  <value name="COLOUR2">
<shadow type="colour_picker">
     <field name="COLOUR">#3333ff</field>
</shadow>
  </value>
  <value name="RATIO">
     <shadow type="math_number">
<field name="NUM">0.5</field>
     </shadow>
  </value>
</block>
</category>
         </xml>
$(document).ready(function () {
$("#connect-to-robot").on('click', addressChanged);
var toolbox = document.getElementById('toolbox');
var blocklyArea = document.getElementById('blocklyArea');
var blocklyDiv = document.getElementById('blocklyDiv');
});

var options = {
toolbox: toolbox,
collapse: true,
comments: true,
disable: true,
maxBlocks: Infinity,
trashcan: true,
horizontalLayout: false,
toolboxPosition: "start",
css: true,
media: "Content/media/",
rtl: false,
scrollbars: true,
sounds: true,
oneBasedIndex: false,
grid: {
spacing: 20,
length: 1,
colour: "#888",
snap: true
},
zoom: {
controls: true,
wheel: true,
startScale: 1.0,
maxScale: 3.0,
minScale: 0.3,
scaleSpeed: 1.2
}
};

var workspace = Blockly.inject(blocklyDiv, options);

function addressChanged(input) {
ip = validateIPAddress(input);
if (ip !== "") {
$(connectingAnimation).prependTo('#blocklyDiv');
sendGetRequestToRobot("info/help", ip, getCategories);
}
}

function getCategories(data) {
...blah blah code that builds list of categories and list of category objects...
addBlocks(categories, blockDataAssignedToEachCategory
}


function addBlocks(categories, blockDataAssignedToEachCategory) {
//start appending category tags to #toolbox
for (var y = 0; y < categories.length; y++) {
let blocks = AjaxData[y];
var Misty = document.createElement("category");
Misty.setAttribute("name", categories[y]);
Misty.setAttribute("colour", colours[y]);
for (var i = 0; i < getBlocks.length; i++) {
addBlock(block);
}
toolbox.appendChild(Misty);
}
workspace.updateToolbox(toolbox);
}

//#Add blocks and tabs to UI

function addBlock(block) {
var mistyBlock = document.createElement("block");
mistyBlock.setAttribute("type", blockName);
mistyBlock.setAttribute("disabled", false);
Misty.appendChild(mistyBlock);
Blockly.Blocks[block] = {
init: function () {
this.setColour(colour);
var dummy = this.appendDummyInput();
dummy.appendField(block.name);
dummy.appendField(block.args);
let fieldValue = "FIELD_" + blockName + "_" + argsAsStrings[k];
dummy.appendField(fieldType, fieldValue);
addFieldsToBlockxml(block, fields);
}
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setOutput(true);
this.setTooltip(blockName);
}
}


Blockly.JavaScript[blockName] = function (block) {
const payload = {};
var httpRequest;
for (const arg of argsAsStrings) {
var input = parseInt(block.getFieldValue("FIELD_" + blockName + "_" + arg));
payload[arg] = input;
};
var code;
console.log(payload);
if (httpVerb === "GET") {
code = "sendGetRequestToRobot(\"" + endpoint + "\",\"" + ip + "\");";
} else {
code = "sendPostRequestToRobot(\"" + endpoint + "\",\"" + ip + "\"," + JSON.stringify(payload) + ");";
}

return code;
};
};

Erik Pasternak

unread,
Mar 13, 2018, 5:26:00 PM3/13/18
to Blockly
Hi Erin,

Are the parts of the code you've included accurate or do they include some pseudocode/shorthand?

Specifically, I see a few errors that should break quite badly long before you get to deleting blocks:

addBlock(block); in function addBlocks --- block is not defined.

Blockly.Blocks[block] = { -- block is an object, it should use blockName (which is also not defined).

Could you copy paste the relevant functions exactly as they are in your code?

Thanks,
Erik

Erik Pasternak

unread,
Mar 13, 2018, 6:02:18 PM3/13/18
to Blockly
And a quick followup.

I looked through the code around that error. It comes from workspace.removeTopBlock which is only called in Block.dispose or Block.setParent. Since you're hitting this when you try to delete a stack, it's probably that something went wrong in the dispose case.

You might want to step through that code and check if either:
  1. The top block in the stack doesn't exist in the workspace. If so, you may have broken how blocks get dragged out of the toolbox.
  2. One of the sub-blocks isn't being added as a top block before it's disposed of, which should happen in Block.unplug (which is called by dispose).
Once you track down the immediate cause we can help dig into why that might be broken.

Cheers,
Erik

Erin Connolly

unread,
Mar 13, 2018, 6:20:33 PM3/13/18
to Blockly
Hi Erik, 

yes, LOTS of psuedocode.  I can copy and paste all of the relevant functions (there may be a few mistakes as I had to quickly adjust to remove proprietary information):

Here they are:
Index.html

<div id="blocklyArea" class="container-fluid" style="height:95%positionrelative;">
						<div id="blocklyDiv" class="col-xs-12" style="height:100%;">
						</div>
					</div>
					<xml id="toolbox" style="display:none;"><category name="Blockly Blocks" colour="#4285F4">
							<block type="controls_if"></block>
							<block type="logic_compare">
								<field name="OP">EQ</field>
							</block>
</category></xml>
		<script type="text/javascript" src="Content/Blockly/js/blockly_compressed.js"></script>
		<script type="text/javascript" src="Scripts/javascript_compressed.js"></script>
		<script type="text/javascript" src="Content/Blockly/js/blocks_compressed.js"></script>
		<script type="text/javascript" src="Content/Misty/js/misty_helper_functions.js"></script>
		<script type="text/javascript" src="Content/Misty/js/misty_ajax.js"></script>
		<script type="text/javascript" src="Content/Misty/js/misty_blocks.js"></script>
		<script type="text/javascript" src="Content/Blockly/js/msg/en.js"></script>
		<script type="text/javascript" src="Content/Blockly/js/msg/messages.js"></script>
		<script type="text/javascript" src="Content/Blockly/js/acorn_interpreter.js"></script>
		<script type="text/javascript" src="Content/Misty/js/misty_index_setup.js"></script>

misty-index-setup.js
$(document).ready(function () {
	$("#connect-to-robot").on('click'
, addressChanged);
	$("#abort-script").on("click", abortFunction);
	$("#show-javascript").on('click', showJavaScript);
	$("#export-session").on('click', exportBlocklySession);
	$("#run-script").on('click', runJavaScript);
	$("#browse-files").on('click', openFilePicker);
	
var onresize = function (e) {
	// Compute the absolute coordinates and dimensions of blocklyArea.
	var element = blocklyArea;
	var x = 0;
	var y = 0;
	do {
		x += element.offsetLeft;
		y += element.offsetTop;
		element = element.offsetParent;
	} while (element);
	// Position blocklyDiv over blocklyArea.
	blocklyDiv.style.left = x + 'px';
	blocklyDiv.style.top = y + 'px';
	blocklyDiv.style.width = blocklyArea.offsetWidth + 'px';
	blocklyDiv.style.height = blocklyArea.offsetHeight + 'px';
};
window.addEventListener('resize', onresize, false);
onresize();
Blockly.svgResize(workspace);
$(document).ready(function () {
	$("#connect-to-robot").on('click'
, addressChanged);
	$("#abort-script").on("click", abortFunction);
	$("#show-javascript").on('click', showJavaScript);
	$("#export-session").on('click', exportBlocklySession);
	$("#run-script").on('click', runJavaScript);
	$("#browse-files").on('click', openFilePicker);
	
var onresize = function (e) {
	// Compute the absolute coordinates and dimensions of blocklyArea.
	var element = blocklyArea;
	var x = 0;
	var y = 0;
	do {
		x += element.offsetLeft;
		y += element.offsetTop;
		element = element.offsetParent;
	} while (element);
	// Position blocklyDiv over blocklyArea.
	blocklyDiv.style.left = x + 'px';
	blocklyDiv.style.top = y + 'px';
	blocklyDiv.style.width = blocklyArea.offsetWidth + 'px';
	blocklyDiv.style.height = blocklyArea.offsetHeight + 'px';
};
window.addEventListener('resize', onresize, false);
onresize();
Blockly.svgResize(workspace);
$(document).ready(function () {
	$("#connect-to-robot").on('click'
, addressChanged);
	$("#abort-script").on("click", abortFunction);
	$("#show-javascript").on('click', showJavaScript);
	$("#export-session").on('click', exportBlocklySession);
	$("#run-script").on('click', runJavaScript);
	$("#browse-files").on('click', openFilePicker);
	
var onresize = function (e) {
	// Compute the absolute coordinates and dimensions of blocklyArea.
	var element = blocklyArea;
	var x = 0;
	var y = 0;
	do {
		x += element.offsetLeft;
		y += element.offsetTop;
		element = element.offsetParent;
	} while (element);
	// Position blocklyDiv over blocklyArea.
	blocklyDiv.style.left = x + 'px';
	blocklyDiv.style.top = y + 'px';
	blocklyDiv.style.width = blocklyArea.offsetWidth + 'px';
	blocklyDiv.style.height = blocklyArea.offsetHeight + 'px';
};
window.addEventListener('resize', onresize, false);
onresize();
Blockly.svgResize(workspace);
function addressChanged(e) {
		//connect to robot, get capabilities
		e.preventDefault();
		e.stopPropagation();
		listOfEnabledBlocks = [];
		capabilities = [];
		audioAssetsCallMade = false;
		var input = $("#ip-address").val();
		try {
			updateBlockValidity(input);
		}
		catch (err) {
			console.log("Error updating block validity:" + err);
		}
	};
 
	
 
function updateBlockValidity(input) {
	ip = validateIPAddress(input);
	
if (ip !== "") {
		$(connectingAnimation).prependTo('#blocklyDiv'
);
		sendGetRequestToRobot("info/help", ip, getCommandNames);
	}
}
 
function validateIPAddress(input) {
	// right now we only except ip addresses.
	var ipNumbers = input.split('.');
	var ipNums = new Array(4);
	var ip = "";
	if (ipNumbers.length !== 4) {
		console.error("IP Address needs to be in the format of ###.###.###.### where ### is a number between 0-255.");
		return "";
	}
	for (let i = 0; i < 4; i++) {
		ipNums[i] = parseInt(ipNumbers[i]);
		if (ipNums[i] < 0 || ipNums[i] > 255) {
			console.error("IP Address needs to be in the format of ###.###.###.### where ### is a number between 0-255.");
			return "";
		}
	}
	ip = ipNums.join('.');
	return ip;
};

misty-blocks.js
function getCommandNames(result) {
	if (result !== ""
) {
		let allCommandsOnRobot = [];
		let theDataObject = JSON.parse(result);
		$.each(theDataObject, function (i, com) {
			if ($.inArray(com, allCommandsOnRobot) === -1) { allCommandsOnRobot.push(com) }
		});
		let CommandInfo = [],  commands = [];
		
for (let index of allCommandsOnRobot) {
			!notYetImplemented.includes(index) ? commands.push(index) : null
;
		}
		getArguments(commands, CommandInfo);
	}
}
 
function getArguments(commands, CommandInfo) {
	
if (commands.length) {
		let endpoint = "info/help?command=" + commands[0];
		sendGetRequestToRobot(endpoint, ip, function (result) {
			let theDataObject =
 JSON.parse(result);
			commands.shift();
			getArguments(commands, CommandInfo);
		});
	} else {
		$("#connecting-animation").remove();
		$("#connect-to-robot").html("Connected").removeClass("disconnected").addClass("connected");
		monitorConnectionStatus =
			setInterval(function () {
				sendGetRequestToRobot("info/device", ip)
			}, 2000);
		enableButtons();
		constructCategories(CommandInfo);
	}
};
 
//#generate categories (aka tabs) from robot

function constructCategories(CommandInfo) {
	var commandGroupOfEachCommand = CommandInfo.map(x => x.ApiCommand.ApiCommandGroup)
	const CommandGroups = {}; 
	for (const key of commandGroupOfEachCommand) {
		!CommandGroups[key] ? CommandGroups[key] = new Array : null;
	};
	var categoryArray = Object.keys(postCommandGroups);
 
	for (var i = 0; i < categoryArray.length; i++) {
		for (var j = 0; j < CommandInfo.length; j++) {
			CommandInfo[j].ApiCommand.ApiCommandGroup === categoryArray[i] ? CommandGroups[categoryArray[i]].push(CommandInfo[j]) : null
		};
	};
	constructCommandObjects(CommandGroups, categoryArray);
}
 
function constructCommandObjects(CommandGroups,  categories) {
	let CommandObjects = []; 
	
for (var k = 0; k < categories.length; k++
) {
		let completeCategory = [];
		let Group = CommandGroups[categories[k]]
		for (var x = 0; x < Group.length; x++) {
			
let command = {
				"Name": postGroup[x].ApiCommand.Name,
				"Endpoint": postGroup[x].Endpoint,
				"Arguments"
: postGroup[x].ApiCommand.Arguments,
			}
			completeCategory.push(command);
		}
		if (categories[k] === "Stuff") {
			completeCategory.push({ "Name""abc""Arguments"null })
		}
		if (categories[k] === "Other Stuff") {
			completeCategory.push({ "Name""xyz""Arguments"null })
		}
		CommandObjects.push(completeCategory);
		
	}
	updateMistyBlocks(categories, CommandObjects);
}
 
 
 
function updateMistyBlocks(categories, CommandObjects) {
	
var colours = ["#745CA6""#FBBD0B""#D11149""#4285F4""#745CA6""#E6C229""#F17105""#1A8FE3""#6B09EE"];
	var blocklyColours = [260463442182604828206266
];
	while (toolbox.childNodes[3]) {
		toolbox.removeChild(toolbox.childNodes[3]);
	}
	
//start appending category tags to #toolbox
	for (var y = 0; y < categories.length; y++
) {
		Blocks = CommandObjects[y];
		
var Misty = document.createElement("category");
		Misty.setAttribute("name", categories[y]);
		Misty.setAttribute("colour"
, colours[y]);
		for (var i = 0; i < Blocks.length; i++) {
			addBlock(Blocks[i], Misty, blocklyColours[y]);
		
		toolbox.appendChild(Misty);
	}
	workspace.updateToolbox(toolbox);
}
 
//#Add blocks and tabs to UI

function addBlock(block, Misty, colour) {
	var blockName = block.Name;
	
var endpoint = block.Endpoint;
	var legacyCommands = Object.keys(hardCodedCommands);
	var commandArguments = block.Arguments;
	var argsAsStrings = commandArguments ? Object.keys(commandArguments) : "";
	var parameters = Array.from(argsAsStrings, x => commandArguments[x]);
	var xmlFields =
 [];
	
var mistyBlock = document.createElement("block");
	mistyBlock.setAttribute("type", blockName);
	mistyBlock.setAttribute("disabled"false
);
	Misty.appendChild(mistyBlock);
 
	if (!arrayContains(legacyCommands, blockName)) {
		Blockly.Blocks[blockName] = {
			init: function () {
				
this.setColour(colour);
				var dummy = this
.appendDummyInput();
				dummy.appendField(blockName);
				
for (var k = 0; k < argsAsStrings.length; k++) {
					var argDetails =
 parameters[k];
					dummy.appendField(argDetails.Name);
					
let fieldValue = "FIELD_" + blockName + "_" +
 argsAsStrings[k];
					
let typeInfo = argDetails.GetValueType;
					let type = typeInfo.substring(7, typeInfo.indexOf(","));
					let fieldTypes = blocklyTypes(argDetails.Value, -100100);
					let fieldType =
 fieldTypes[type];
					dummy.appendField(fieldType, fieldValue);
					addFieldsToBlock(mistyBlock, xmlFields, fieldValue);
				}
				
this.setPreviousStatement(truenull);
				this.setNextStatement(truenull);
				this.setOutput(true);
				this.setTooltip(blockName);
			}
		}
 
 
		Blockly.JavaScript[blockName] = function (block) {
			const payload = {};
			var httpRequest;
			for (const arg of argsAsStrings) {
				var input = parseInt(block.getFieldValue("FIELD_" + blockName + "_" + arg));
				payload[arg] = input;
			};
			var code;
			console.log(payload);
			if (httpVerb === "GET") {
				code = "sendGetRequestToRobot(\"" + endpoint + "\",\"" + ip + "\");";
			} else {
				code = "sendPostRequestToRobot(\"" + endpoint + "\",\"" + ip + "\"," + JSON.stringify(payload) + ");";
			}
 
			return
 code;
		};
	} else {
		legacyBlocks(block, Misty, blockName, mistyBlock, xmlFields, argsAsStrings, colour, endpoint);
	}
};

Erin Connolly

unread,
Mar 13, 2018, 6:23:20 PM3/13/18
to Blockly
Erik, 

I have stepped through quite a few times, comparing to another project that has hardcoded blocks, and I don't feel a whole lot smarter as a result.  However, I can say, that #2 in the scenarios you listed seems to be the case. That is why removeTopBlock is throwing that error when it gets to the second block.  Any ideas why it is not being added?

Thank you,
Erin

Erin Connolly

unread,
Mar 13, 2018, 6:26:15 PM3/13/18
to Blockly
Sorry that document.ready function is as follows:

$(document).ready(function () {
	$("#connect-to-robot").on('click'
, addressChanged);
	$("#abort-script").on("click", abortFunction);
	$("#show-javascript").on('click', showJavaScript);
	$("#export-session").on('click', exportBlocklySession);
	$("#run-script").on('click', runJavaScript);
	$("#browse-files").on('click', openFilePicker);
	
var toolbox = document.getElementById('toolbox');
	var blocklyArea = document.getElementById('blocklyArea');
	var blocklyDiv = document.getElementById('blocklyDiv');
});
Andrew,

--
Andrew</font

Erik Pasternak

unread,
Mar 15, 2018, 4:55:36 PM3/15/18
to Blockly
Sorry for the slow reply. Haven't had much time to look at this.

It might be because you're setting both previous and output to true on your blocks. This should be an error in Blockly and you would have needed to remove some checks to allow it. The issue is we assume there's only one path up to the parent, so unplug() has an if/else if check.

Allowing both output and previous means a block could have two parents and lead to circles in the block hierarchy. A lot of Blockly is optimized because the tree of blocks is directed and introducing a circle will break the code in a bunch of places.

This might just be a copy paste error in the code though, so let me know if that's not the issue.

Additional note, toolbox.removeChild(toolbox.childNodes[3]); is not necessary. Calling updateToolbox with the new tree will replace everything.

Cheers,
Erik
Reply all
Reply to author
Forward
0 new messages