custom block generator: figuring out the intended contents of a shadow input

86 views
Skip to first unread message

Tim Erickson

unread,
May 26, 2023, 10:29:57 AM5/26/23
to Blockly
Head vs. wall, brief background:

I have a custom block that lets the user type in a probability and two possible results; it returns one of the two results depending on the probability.

The first implementation worked great; I used fields for all three values, and therefore used 

let prob = block.getFieldValue('PROB') 

or the equivalent, to retrieve the values. 

Wrinkle: for the probability, the user can type in a number ("0.42"), a percentage ("42%"), or a fraction ("3/7"); I parse the string input to determine the numeric value that I use to determine the output.

In any case, the flow was roughly this:

let prob = block.getFieldValue('PROB');
let one = block.getFieldValue('ONE');
let two = block.getFieldValue('TWO');
const theNumericValue = parseProbability(prob);
code = `Math.random() < ${theNumericValue} ? "${one}" : "${two}"`

NOW I want to change the probability from a field to an input. I made a sweet (text) shadow block with "1/2" as my default, and changed to

prob = Blockly.JavaScript.valueToCode(block...)

probBlockDefault.png
to retrieve what the user had typed. This is apparently not what I should do, because: 
  • Instead of prob being "1/2", a regular string, it's now '"1/2"', that is,  a string with the code that you would normally insert into the code you're generating. I made a kludge to strip one layer of quotes. There must be a better way.
  • Worse, if the user plops a variable into that socket, e.g., myProb, valueToCode (of course) returns "myProb", a string with the name of the variable. Which makes sense, but it means I should not parse that string, but just insert it directly into the statement that compares it with the random number.
sampleProbBlocks.png<-- fails.
What's the right way to solve this problem? Is there a way to tell, when the input might be a string or might be a variable, which one it is? if I knew, I could construct the appropriate `code`. 

Thanks in advance, sorry to be such a noob!

Tim

Maribeth Bottorff

unread,
May 26, 2023, 12:54:12 PM5/26/23
to Blockly
I think the problem is that strings are not the correct data type for this use case. I would create a fraction field that can parse the various types of decimal/fraction/percent inputs and returns a javascript number as the value of the field. Then create a fraction block that uses the fraction field. If you call valueToCode on such a block, you'd get back e.g. '0.42', a string that you can add directly to the rest of the generated code for the parent block. That way you would be treating all blocks equally, whether they are your fraction blocks, variables, or a chain of regular number blocks like a division block with 1 and 2 to make 1/2. You can use connection types to prevent connecting string or list blocks to this input. Using a number block is also more straightforward for learners to understand. Using a string block could be confusing and you would need to deal with people typing in non-numeric strings.

Here's another approach. I don't think you should do this, because it would make your generated code more complicated which can be confusing to learners, but I'm explaining it because I think it helps explain the mind-bending you have to do to understand returning code as strings. If you want the values to be strings, you could do the `parseProbability` function in the generated code instead. That is, the generated code includes a utility function that does the parsing there. The reason it's tricky to do this parsing at code generation time is that you actually don't always know the type of the connected blocks at code generation time. Imagine that the variable "myProb" is set to the string block "1/2". In that case, you would need to do the string logic parsing, but at runtime (i.e. in the generated code) as you won't know the value of "myProb" until then. If you simply detected that the `valueToCode` call returned the name of a variable, and then inserted that variable name into "Math.random() < myProb" then the code would fail to work as intended if "myProb" is a string. This puts you in the weird situation where your shadow block hints to the user they should use a string, but if they try to set a variable to that same string, it doesn't work.

In summary, you must either:
- Never handle strings, by creating your own number blocks
- Always handle strings, including those in variables, by doing string parsing at runtime instead of code generation time

I hope that made sense and is helpful. Best of luck.
Maribeth

Tim Erickson

unread,
May 29, 2023, 12:18:47 AM5/29/23
to blo...@googlegroups.com
I love this idea! I will see if I can figure out how to implement it..

Thanks again for all your help!


On May 26, 2023, at 9:54 AM, 'Maribeth Bottorff' via Blockly <blo...@googlegroups.com> wrote:

I think the problem is that strings are not the correct data type for this use case. I would create a fraction field that can parse the various types of decimal/fraction/percent inputs and returns a javascript number as the value of the field. Then create a fraction block that uses the fraction field. If you call valueToCode on such a block, you'd get back e.g. '0.42', a string that you can add directly to the rest of the generated code for the parent block. That way you would be treating all blocks equally, whether they are your fraction blocks, variables, or a chain of regular number blocks like a division block with 1 and 2 to make 1/2. You can use connection types to prevent connecting string or list blocks to this input. Using a number block is also more straightforward for learners to understand. Using a string block could be confusing and you would need to deal with people typing in non-numeric strings.

………
Tim Erickson (he, him, …)
eepsmedia at gmail dot com





Tim Erickson

unread,
May 29, 2023, 1:31:59 PM5/29/23
to blo...@googlegroups.com
Success!  I’m not sure I actually created a fraction “field”; I’m using field_input (instead of field_number) in my definition of a block I call math-number-fraction. If I wanted to create a new kind of field (e.g., field_fraction) where would I look? But I encapsulated the fraction-parsing stuff into the generator for the fraction block, which worked great!

If you want to see how well your idea works, try:


Press the green “play” button to run the code; the graph and table on the right will update. If you change the probability (fraction, decimal, or percent) in the declaration of `prob`, and run it, you can see the change in the results. If you are familiar with CODAP, there’s a lot more you can do :)

Cheers and thanks,

Tim

P.S.

Tim Erickson

unread,
May 29, 2023, 4:44:11 PM5/29/23
to blo...@googlegroups.com
And naturally, that was not working properly. Back to the drawing board! Something failed to serialize!

On May 29, 2023, at 10:31 AM, Tim Erickson <eeps...@gmail.com> wrote:

Success!  I’m not sure I actually created a fraction “field”; I’m using field_input (instead of field_number) in my definition of a block I call math-number-fraction. If I wanted to create a new kind of field (e.g., field_fraction) where would I look? But I encapsulated the fraction-parsing stuff into the generator for the fraction block, which worked great!

If you want to see how well your idea works, try:


Press the green “play” button to run the code; the graph and table on the right will update. If you change the probability (fraction, decimal, or percent) in the declaration of `prob`, and run it, you can see the change in the results. If you are familiar with CODAP, there’s a lot more you can do :)

Maribeth Bottorff

unread,
May 30, 2023, 12:46:40 PM5/30/23
to Blockly
The bare field_input is not intended to be used directly; it's an abstract class meant only as a superclass of other input-type fields like string and number. That may be why the fields fail to (de?)serialize.

To create a custom field you would want to subclass an existing field (probably field_number, but maybe just field_input) and override the parts you need to change. So if you subclass field_number, you'd want to override `doClassValidation_` to allow fractions/percents, and override widgetCreate_ to not set the html input type to "number" as this will prevent users from typing the "/" or "%" characters. (Note this particular override won't be needed after the next release because we are removing this restriction from core at that time.) You can also override other methods like `getDisplayText_` if you want to always show the user the value in the format they typed in, while still using decimals as the real value used in code generation. You can learn more about customizing fields in the documentation. Start here and also note there is a lot of helpful information in the "Creating a Custom Field" page.

Maribeth

Reply all
Reply to author
Forward
0 new messages