What is *your* opinion about deletion with +/- mutators?

218 views
Skip to first unread message

Beka Westberg

unread,
Mar 25, 2020, 1:10:40 PM3/25/20
to Blockly
Hello,

I've been working on a thing to do with +/- mutators. It might end up being a demo, or it might become a package. Here is what they currently look like:

PlusMinus.png

I modeled these after the OpenRoberta blocks. But because OpenRoberta built their +/- mutators using an older version of blockly, they could only have one plus and one minus on each block. In newer versions that's no longer a limitation. So if we wanted to we could have a plus and a minus on each input. We could even have up /\ and down \/ arrows on each input to move them.

My question is: Do you think each individual input should have a plus and a minus? Just a minus and not a plus? Or do you think the current design is good?

If you were designing +/- mutators for your users, how would you do it?

Any input is appreciated! Thank you for your time :D
--Beka

P.S: Here's the source if anyone wants to take a look.

Coda Highland

unread,
Mar 25, 2020, 1:46:19 PM3/25/20
to blo...@googlegroups.com
It's a bit of a tricky proposition. It seems obvious at first glance -- the - should be on each individual entry that can be removed, why would you do anything else? But thinking through it a bit more it's a little harder.

First off, which will children find more intuitive? Blockly can be used by people of varying levels of technical sophistication, but children are something of a least common denominator. This would probably be best served with a focus group, because I can see arguments for both directions.

Second, which backend API will be better for developers creating blocks? A simple +/- at the top just requires a simple property or getter to determine which buttons should be shown, and two simple stateless callbacks for adding or removing. Knowing which items need a - or not requires that it be defined on a per-value basis, and the - might apply to more than one value, as "else if/do" demonstrates. And the remove callback would need to receive sufficient information to know what's being requested.

The - per value is more flexible for certain, because it means you can choose which value to remove if it's already been populated, which is a bit of a problem with the current scheme -- if Blockly can automatically deal with moving around existing connections, that simplifies the implementation of mutators for the most common use case. And as you mention, if this provides hooks for up and down buttons that will make things nicer for the user as well -- but only if block developers have an easy, clean way to manage it. And there's still the edge case of up/down being applicable in different places than remove, which needs a good API design as well.

If we can pull off the necessary engineering to make the inline -/^/v commands work with a minimum of effort on the block developer's part, if it easily works with "else if / do" kinds of related values, and if it can smooth out the current challenges of building mutators, then I think that's probably the best choice... but that's a lot of "if"s.

/s/ Adam

--
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+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/0d750c9e-6f7d-47ef-a240-6923114c6730%40googlegroups.com.

Beka Westberg

unread,
Mar 25, 2020, 4:05:16 PM3/25/20
to Blockly
Hi Adam!

Thank you for the detailed response :D
 
This would probably be best served with a focus group, because I can see arguments for both directions.

Good idea! Sadly I don't have a focus group at my disposal :( But I'll try to contact the the OpenRoberta people and see if they've done anything like that.

Second, which backend API will be better for developers creating blocks?

So the current API supports both, but that's because it's pretty DIY. Let me lay it out and you can tell me what you think.

mutationToDom and domToMutation remain the same, because this is purely a UI change. The only things I've added are two fields, a plus field and a minus field. On construction each field takes in an arbitrary data object (which can be undefined). The data object is passed to the callback (sourceBlock.plus() for plus and sourceBlock.minus() for minus) when the field is clicked.

So here's an example of the UI for the 'if and 'if else' blocks (they use the same mutator).
And here's an example for the procedures. You can see how in this case the minus is passed an argId which is later used to remove that specific arg.

So what do you think of that API? Is there any way it could be abstracted or made easier to use?

Thank you again for the feed back :D
--Beka
To unsubscribe from this group and stop receiving emails from it, send an email to blo...@googlegroups.com.

Coda Highland

unread,
Mar 25, 2020, 8:16:20 PM3/25/20
to blo...@googlegroups.com
On Wed, Mar 25, 2020 at 3:05 PM Beka Westberg <bekawe...@gmail.com> wrote:
Hi Adam!

Thank you for the detailed response :D

Happy to help!
 
This would probably be best served with a focus group, because I can see arguments for both directions.

Good idea! Sadly I don't have a focus group at my disposal :( But I'll try to contact the the OpenRoberta people and see if they've done anything like that.

I wonder if there's anyone else we could ask.
 
mutationToDom and domToMutation remain the same, because this is purely a UI change.

I expected this, although with new block-level APIs the implementations might hopefully be a little more straightforward for some common cases.
 
The only things I've added are two fields, a plus field and a minus field. On construction each field takes in an arbitrary data object (which can be undefined). The data object is passed to the callback (sourceBlock.plus() for plus and sourceBlock.minus() for minus) when the field is clicked.

So here's an example of the UI for the 'if and 'if else' blocks (they use the same mutator).

I assume you mean API. ;)

This is more or less what I expected to see for having a single minus button for the entire block. 
 
And here's an example for the procedures. You can see how in this case the minus is passed an argId which is later used to remove that specific arg.

Where do you define which values get a - button and which don't? I can only imagine you must have made a change to addVarInput_ that you didn't include in the pastebin. This looks like it COULD be extended to handle if-else but it would take more work on the block developer's part.
 
So what do you think of that API? Is there any way it could be abstracted or made easier to use?

Thank you again for the feed back :D
--Beka

 It's definitely "DIY" as you say. I think there's a lot that could be done. Let me pitch an idea for what I might like to see as an embedder:

Block.setPlusButton(boolean) - enables or disables the + button
Block.setMinusButton(boolean) - enables or disables the block-level - button (if enabled, Input.setMinusButton doesn't render a - button, but Input.linkMinusButton still works)
Input.setMinusButton(boolean) - enables or disables the value-level - button
Input.linkMinusButton(valueName: string) - associates the value with a - button but doesn't add one to the current value
Block.mutatePlus(plusID?: number, data?: mixed) - programmatically clicks the plus button, but optionally allows a specific ID and associated data to be passed to plus()
Block.mutateMinus(valueName?: string) - programmatically clicks the minus button (block-level if no parameter, input-level if a name is passed)
Block.nextPlusID: number - an autoincrementing number passed to plus() and automatically stored in the block XML if nonzero

Callback .plus(nextPlusID: number, data?: mixed) - receives nextPlusID and anything passed to mutatePlus()
Callback .minus(valueName: string) - invoked for the affected input and for any linked inputs

If minus() isn't defined, the default implementation would be to call `this.removeInput(valueName)`. For the block-level - button, it would pass the valueName of the last input on the block, but the developer would be free to ignore that if they want to manage the stack their own way. If the last input is linked it will invoke minus() for any blocks linked with it as well.

The reason mutatePlus() takes optional parameters is to make it easier to reuse the plus() callback -- domToMutation can call mutatePlus() with whatever information it needs to reconstruct the appropriate state. (I'm hard-pressed to think of what might be relevant to pass in the data parameter, but it's trivial to include so I don't see any reason NOT to have it.)

One noteworthy difference from what you have here is that it doesn't require defining an ID for the callback to receive; it just uses the IDs that already have to be there.


This could be kicked up a notch, but it would be quite a bit more ambitious:

Remove Block.nextPlusID
Block.setUseListMutator(boolean) - if true, lift the uniqueness constraint from input names and provide a default implementation for domToMutation and mutationToDom
Block.getInput(name: string, index?: number) - the same as it already is, but distinguish among multiple values with the same name
Block.removeInput(name: string, index?: number, quiet?: boolean) - ditto, but will need some magic to handle the quiet parameter in a backwards-compatible way
Block.getListMutatorCount(): number - returns the number of entries in the mutator list
Block.mutatePlus(data?: mixed): number - plusID is no longer necessary, so remove it; returns the index of the newly-created mutator entry
Block.mutateMinus(index?: number) - takes a mutator index instead of a value name

Callback .plus(index: number, data?: mixed) - same as above, but receives an index instead of nextPlusID
Callback .minus(index: number) - invoked only once instead of once for each linked input; default implementation removes all linked inputs

If useListMutator is enabled, then domToMutation and mutationToDom automatically load/store the number of entries in the mutator list, with domToMutation calling plus() the relevant number of times.

This could become EVEN MORE ambitious if desired, having inputs automagically track their mutator list index if they were created within the plus() callback so that minus() would only be needed if you have additional data structures that have to be kept in sync.


This post may be a little bit rambly and I've got a lot of ideas floating around on different variants of this API -- the core of my idea, though, is that this is probably the #1 most common use of mutators so it should get as much special treatment as possible so that block developers don't have to jump through arcane, poorly-documented, wheel-reinventing hoops to do it.

/s/ Adam

Léo Briand

unread,
Mar 26, 2020, 7:59:47 AM3/26/20
to Blockly
Hi,

First of all, I much more prefer the +/- system over the classic mutator menu, that's also feedback I got from young users.

According to me, it is necessary to have a "-" for each row of elseif and the final else, otherwise it gets really hard to manipulate this block (mostly because it has 2 inputs : else and elseif).
I also prefer "-" at each line for the function block, though it's less obvious. I'm not sure about the chevrons to move up & down.
For texts and lists, I don't believe it is a necessity and I would prefer a single "-" that removes the last element.

A single "+" is enough for all these blocks.
We almost finished implementing these changes, I can share a demo when it's done if that helps anyhow.

Best,
Léo

Beka Westberg

unread,
Mar 26, 2020, 12:46:28 PM3/26/20
to Blockly
Thank you Adam and Léo for your feedback! I'll respond to you both in this post so that I'm not double-posting.

Where do you define which values get a - button and which don't?

So all of the input's that /don't/ get a minus are defined statically in the block's JSON definition. All of the inputs that /do/ get a minus are created using addVarInput_.
Inputs.png













Sorry if that doesn't answer your question :/

 As for the API, I actually think I like the more ambitious API better! I think it would be easier to bundle it into an object, which is useful when this exists as an extension, and not part of core.

I think how AppInventor handles their mutator API might be a good place to look. All of their mutators are "list" type mutators. The block author has to provide a block to use in composition, an addInput() function and an addEmptyInput() function. Everything else (composition, decomposition, serialization, saving connections) is handled automatically.

I don't think it would be hard to create some sort of PlusMinusListMutator() object which acts similarly. The other functions you mentioned could be added easily as well. Then once setMutator is fixed (so that you can pass more than just icons) it could be applied using that.

More "preset" mutators could be added in the same fasion.

How would you feel about that? Basically take that same idea, a simplified API for list mutators, but bundle it into an object?

---


First of all, I much more prefer the +/- system over the classic mutator menu, that's also feedback I got from young users.

Thank you for providing this! It's good to get confirmation that multiple projects find this system useful :D

 a "-" for each row of elseif and the final else, otherwise it gets really hard to manipulate this block

Personally, I prefer including two separate blocks (an "if - else if" and an "if - else if - else" block) rather than allowing the "else" to be manipulated dynamically. Seeing a block like this:
AltIf.png








It seems confusing that it has two plus buttons. Separate "if - else if" and "if - else if - else" blocks is also pretty standard. OpenRoberta, Scratch, and AppInventor, all have separate blocks, and that's just off the top of my head. So I'm pretty comfortable with this decision, but I could be persuaded to go a different route.

For texts and lists, I don't believe it is a necessity and I would prefer a single "-" that removes the last element.

If we decide to go with a minus for each individual element, do you think that would be bad for users? Or just unnecessary?

 We almost finished implementing these changes, I can share a demo when it's done if that helps anyhow.

A demo would be awesome! I'd love to take a look :D Any information is good information!

Thank you guys again for the feedback! I really appreciate it =)
--Beka 

Coda Highland

unread,
Mar 26, 2020, 3:59:36 PM3/26/20
to blo...@googlegroups.com
On Thu, Mar 26, 2020 at 11:46 AM Beka Westberg <bekawe...@gmail.com> wrote:
Where do you define which values get a - button and which don't?

So all of the input's that /don't/ get a minus are defined statically in the block's JSON definition. All of the inputs that /do/ get a minus are created using addVarInput_.
Inputs.png













Sorry if that doesn't answer your question :/

It does answer my question, but I can't say I LIKE the answer. This doesn't really provide a way to put - buttons on an else-if, because you can't cleanly predefine all of the "do" inputs as not needing them.
 
 As for the API, I actually think I like the more ambitious API better! I think it would be easier to bundle it into an object, which is useful when this exists as an extension, and not part of core.

I like it better too, but when I say it's more ambitious, that means it's going to require changes to the core, because the uniqueness constraint on inputs gets broken. I like the idea of making it an extension; I'm just not sure it CAN be.
 
I think how AppInventor handles their mutator API might be a good place to look. All of their mutators are "list" type mutators. The block author has to provide a block to use in composition, an addInput() function and an addEmptyInput() function. Everything else (composition, decomposition, serialization, saving connections) is handled automatically.

I'm not a huge fan of the specifics of that API but it's certainly another source of inspiration.
 
I don't think it would be hard to create some sort of PlusMinusListMutator() object which acts similarly. The other functions you mentioned could be added easily as well. Then once setMutator is fixed (so that you can pass more than just icons) it could be applied using that.

My biggest remaining concern once the block itself gets ironed out is how the code generators are going to work. The generator needs an easy, clean way to iterate over the values while keeping them in order and appropriately linked together (for elseif/do). My brainstorming API suggestion did this by putting an index parameter on getInput() so it was a simple numeric for loop, but if we can't modify core, then... I'd like to avoid making block developers have to write boilerplate string manipulation if possible.
 
More "preset" mutators could be added in the same fasion.

Definitely a +1 to anything that can achieve that goal.
 
How would you feel about that? Basically take that same idea, a simplified API for list mutators, but bundle it into an object?

As mentioned above... if we CAN do it, then sure! I'm just worried about the feasibility. 

/s/ Adam 

Léo Briand

unread,
Mar 27, 2020, 6:05:22 AM3/27/20
to Blockly
Regarding the double "+" in if/else, I agree it's a wrong way to go.
We decided to go in the same way makecode did, which is the following - there is only one "+" button, that adds "else" when it's not there already, and "elseif" otherwise.
That's not so easy to catch when described but super intuitive when being used.

If we decide to go with a minus for each individual element, do you think that would be bad for users? Or just unnecessary?
I think that would be visually disurbing, especially for inline lists and texts blocks but not so much for multiline block, so I would probably stick with just one "-". Does it make sense?

I'll forward you the demo when it's ready.
Best,
Léo

Daniel Ehren

unread,
Mar 27, 2020, 7:34:08 AM3/27/20
to Blockly
I really appreciate the first 3 block types. It seems very intuitive for me as a user. I also think that one minus mutator for each parameter is to much. 

Beka Westberg

unread,
Mar 27, 2020, 11:56:37 AM3/27/20
to Blockly
Hello again,

It does answer my question, but I can't say I LIKE the answer. This doesn't really provide a way to put - buttons on an else-if, because you can't cleanly predefine all of the "do" inputs as not needing them.

Adding minuses to the non-do inputs would be possible. We'd just have to redefine addPart_ to look like this:

  addPart_: function() {
   
this.elseIfCount_++;
   
var minus = new FieldMinus(/* pass something here */);
   
this.appendValueInput('IF' + this.elseIfCount_)
       
.setCheck('Boolean')
       
.appendField(minus, 'MINUS'/* + some id */)
       
.appendField(Blockly.Msg['CONTROLS_IF_MSG_ELSEIF']);
   
this.appendStatementInput('DO' + this.elseIfCount_)
       
.appendField(Blockly.Msg['CONTROLS_IF_MSG_THEN']);


   
// Handle if-elseif-else block.
   
if (this.getInput('ELSE')) {
     
this.moveInputBefore('ELSE', /* put at end */ null);
   
}
 
},

I'll admit it doesn't really look like an 'API' though.

I like it better too, but when I say it's more ambitious, that means it's going to require changes to the core, because the uniqueness constraint on inputs gets broken. I like the idea of making it an extension; I'm just not sure it CAN be.

I think we could figure out a way to allow you to interact with things using an index without editting the core. My first instinct would be to have the (theoretical) mutator object keep a list of UUIDs for the inputs. Maybe getInput would look something like:

MutatorObject.getInput = function(index) {
 
var id = this.inputIds[index];
 
return this.sourceBlock.getInput(id);
}

So you would call: block.mutator.getInput(index) instead of block.getInput(name).

There would have to be work done to get it to work with multiple types of inputs (i.e. if & do). But my point is that I think your idea is definitely achievable in an extension format. Even if under-the-hood doesn't look so nice, I think a good facade could be built.

The generator needs an easy, clean way to iterate over the values while keeping them in order and appropriately linked together

Totally getcha, there definitely needs to be a way to handle generators cleanly.

If we went with an ID array we could always allow the developer to loop over every input via the mutator:

generator = function (block) {
 
// Add initial code
 
for (var i = 0, input; input = block.mutator.getInput(i); i++) {
   
// Add input code.
 
}
 
return code;
}

I think that's similar enough to how generators are now that it could work. And it doesn't seem like too much boilerplate.

We decided to go in the same way makecode did, which is the following - there is only one "+" button, that adds "else" when it's not there already, and "elseif" otherwise.

Thanks for mentioning that MakeCode uses +/- mutators  Léo! I had no clue! I do like the MakeCode version of the if-else block. Definitely more intuitive than I thought it'd be (like you said)! My only problem with it is how you have to keep chasing the + button to add statements. You could move the + to the top, but that might make the plus less intuitive... I'm still not sure how I want to handle these blocks hehe.

I'll try and contact the make code people and see if they have anything to add to this discussion :D

I really appreciate the first 3 block types. It seems very intuitive for me as a user. I also think that one minus mutator for each parameter is to much. 

Thank you for your input! It is much appreciated =)

To summarize; it feels like currently we're leaning towards:
- A minus on each input for the if - else if and if - else if - else blocks.
   - How to handle elses is still undecided.
- Only a single minus on join text and create list.
- A minus on each parameter for procedures.

Thank you once again for your responses!
--Beka 

Coda Highland

unread,
Mar 27, 2020, 12:53:32 PM3/27/20
to blo...@googlegroups.com
On Fri, Mar 27, 2020 at 10:56 AM Beka Westberg <bekawe...@gmail.com> wrote:
Hello again,

It does answer my question, but I can't say I LIKE the answer. This doesn't really provide a way to put - buttons on an else-if, because you can't cleanly predefine all of the "do" inputs as not needing them.

Adding minuses to the non-do inputs would be possible. We'd just have to redefine addPart_ to look like this:

I'll admit it doesn't really look like an 'API' though.

That's why I was suggesting some straightforward method on the Input class to enable/disable it. If we have to work in an extension that's not so convenient, but if we're using automagic detection by requiring everything to happen in a mutatePlus() method then we could just put it on the first input added in the plus() callback.
 
I like it better too, but when I say it's more ambitious, that means it's going to require changes to the core, because the uniqueness constraint on inputs gets broken. I like the idea of making it an extension; I'm just not sure it CAN be.

I think we could figure out a way to allow you to interact with things using an index without editting the core. My first instinct would be to have the (theoretical) mutator object keep a list of UUIDs for the inputs. Maybe getInput would look something like:

MutatorObject.getInput = function(index) {
 
var id = this.inputIds[index];
 
return this.sourceBlock.getInput(id);
}

So you would call: block.mutator.getInput(index) instead of block.getInput(name).

There would have to be work done to get it to work with multiple types of inputs (i.e. if & do). But my point is that I think your idea is definitely achievable in an extension format. Even if under-the-hood doesn't look so nice, I think a good facade could be built.

At that point, block.mutator.getInput(name, index) would work. We'd also probably need to wrap append*Input, and removeInput should probably instead be removeInputs(index) to delete an entire group of inputs (it makes little sense to remove a single one).

I didn't expect the under-the-hood to look nice, but that's the whole point of building a library, isn't it? If it was nice and obvious then we wouldn't need it. :P
 
The generator needs an easy, clean way to iterate over the values while keeping them in order and appropriately linked together

Totally getcha, there definitely needs to be a way to handle generators cleanly.

If we went with an ID array we could always allow the developer to loop over every input via the mutator:

I think that's similar enough to how generators are now that it could work. And it doesn't seem like too much boilerplate.

I think block.mutator.getCount() might be a better way of going about it, but something that straightforward is definitely what I had in mind.
 
We decided to go in the same way makecode did, which is the following - there is only one "+" button, that adds "else" when it's not there already, and "elseif" otherwise.

Thanks for mentioning that MakeCode uses +/- mutators  Léo! I had no clue! I do like the MakeCode version of the if-else block. Definitely more intuitive than I thought it'd be (like you said)! My only problem with it is how you have to keep chasing the + button to add statements. You could move the + to the top, but that might make the plus less intuitive... I'm still not sure how I want to handle these blocks hehe.

I'll try and contact the make code people and see if they have anything to add to this discussion :D

Putting the + at the bottom certainly has its... pun intended... pluses and minuses. It's worth thinking about!
 
To summarize; it feels like currently we're leaning towards:
- A minus on each input for the if - else if and if - else if - else blocks.
   - How to handle elses is still undecided.
- Only a single minus on join text and create list.
- A minus on each parameter for procedures.

From the perspective of the extension, that means the extension needs to support both minus behaviors. This shouldn't be a problem.

I am also of the opinion that the extension should be able to toggle between the behaviors, because different embedders may have different opinions -- it may be the case that different audiences have different preferences.

As for handling elses... that's a really good point. Maybe we need to wrap moveInputBefore or add a property defining the insertion point, or maybe the last else/do in the list should have its condition input hidden via setVisible() and then redisplayed when you add a new branch.

/s/ Adam

Rachel Fenichel

unread,
Mar 27, 2020, 1:48:21 PM3/27/20
to Blockly
One idea that may help simplify things: rather than trying to have a single block that can do all of these behaviours in one go, you could make different blocks for different intended use cases.  We already have that in the simple playground, where we have if/else blocks that you stack rather than using mutators.

Beka Westberg

unread,
Mar 27, 2020, 2:52:55 PM3/27/20
to Blockly
rather than trying to have a single block that can do all of these behaviours in one go, you could make different blocks for different intended use cases

I like this idea! What kinds of different blocks would you suggest creating? 

Coda Highland

unread,
Mar 27, 2020, 3:14:38 PM3/27/20
to blo...@googlegroups.com
On Fri, Mar 27, 2020 at 1:52 PM Beka Westberg <bekawe...@gmail.com> wrote:
rather than trying to have a single block that can do all of these behaviours in one go, you could make different blocks for different intended use cases

I like this idea! What kinds of different blocks would you suggest creating? 

I'm not opposed to the idea in the general concept, but I think that some use cases need SOMETHING dynamic, regardless.

The create list block, for example, certainly doesn't need a mutator UI. I've got this working in my own project already: if the last input has a block attached to it, I add another one. If there are two empty inputs at the end, I remove one.

The same can (arguably) be done for function parameters, although my own project takes a COMPLETELY different approach, in no small part because I use Scratch-style start blocks instead of C-shaped function definition blocks.

However, nested if/else blocks is a terrible experience for everyone involved. It WORKS, certainly, and for the least-experienced beginner it might be a step more intuitive, but anyone who's built nontrivial logic in any language that can't express else-if without extra visual nesting can attest that it's hard to read and a terrible waste of screen space. So even though this is the hardest use case to accommodate, I think it's also the most important one.

/s/ Adam

Rachel Fenichel

unread,
Mar 27, 2020, 4:40:13 PM3/27/20
to Blockly
> However, nested if/else blocks is a terrible experience for everyone involved. It WORKS, certainly, and for the least-experienced beginner it might be a step more intuitive, but anyone who's built nontrivial logic in any language that can't express else-if without extra visual nesting can attest that it's hard to read and a terrible waste of screen space. So even though this is the hardest use case to accommodate, I think it's also the most important one.

This is very much an audience question.  If the goal is to build nontrivial logic, and your users already know what they want to build, then nested if/else is bad.  It gets in the way of expressing an idea and is really frustrating.  

But if the goal is to get young users started fast, it's less bad (in my opinion).  They eventually get annoyed by it, as the scratch forums attest, but the ease of use for beginners can be worth the trade-off.

Part of the reason our mutators look the way they do is because we aimed to make them more capable rather than more understandable.  (We also really didn't intend for everyone to assume that they're our suggested implementation, but that's a messaging issue.)

> I like this idea! What kinds of different blocks would you suggest creating? 

At minimum, I would split into two use cases:
- input reordering is required
- input reordering is not required

I think I would split apart function blocks from the rest of the problem, because of the connection between mutating a definition and mutating a caller.

I would also consider Adam's implementation for the list block.  It's still a mutator, just without the mutator UI.  Are there use cases where you can just automatically do the "right" thing?

And I would split if/else blocks into a set for beginnings and a set for advanced users, which matches what we've already done in Blockly.  Maybe the beginners only get +/-, and the advanced users get chevrons as well.

Coda Highland

unread,
Mar 27, 2020, 6:10:29 PM3/27/20
to blo...@googlegroups.com
On Fri, Mar 27, 2020 at 3:40 PM 'Rachel Fenichel' via Blockly <blo...@googlegroups.com> wrote:
> However, nested if/else blocks is a terrible experience for everyone involved. It WORKS, certainly, and for the least-experienced beginner it might be a step more intuitive, but anyone who's built nontrivial logic in any language that can't express else-if without extra visual nesting can attest that it's hard to read and a terrible waste of screen space. So even though this is the hardest use case to accommodate, I think it's also the most important one.

This is very much an audience question.  If the goal is to build nontrivial logic, and your users already know what they want to build, then nested if/else is bad.  It gets in the way of expressing an idea and is really frustrating.  

But if the goal is to get young users started fast, it's less bad (in my opinion).  They eventually get annoyed by it, as the scratch forums attest, but the ease of use for beginners can be worth the trade-off.

I certainly agree, which is why it's a good idea to have an if/else block in the toolbox without needing to dive into the mutator. But that block can, internally, be an if/elseif/else block without any elseifs attached.
 
Part of the reason our mutators look the way they do is because we aimed to make them more capable rather than more understandable.  (We also really didn't intend for everyone to assume that they're our suggested implementation, but that's a messaging issue.)

Yeah, I know; the problem is when you have people like me who are experienced developers that still have to sit there and scratch their heads a bit to figure out how you're supposed to use them for our own blocks. 
 
> I like this idea! What kinds of different blocks would you suggest creating? 

At minimum, I would split into two use cases:
- input reordering is required
- input reordering is not required

I think reordering is never really required. It's very much a "nice to have" but the only time you can't do it the old-fashioned way with a bunch of dragging and dropping is if the block is too dynamic and changes out from underneath you when you disconnect a value. (This is a caveat for the list implementation!) We can build upon that idea later once we get the basic list mutator use case hammered out.
 
I think I would split apart function blocks from the rest of the problem, because of the connection between mutating a definition and mutating a caller.

I think this isn't necessarily a problem; as long as the callbacks are sufficiently robust it shouldn't need special-cased implementation. On the other hand, I don't see a problem with leaving function blocks with their existing implementation while we focus on the other use cases.
 
And I would split if/else blocks into a set for beginnings and a set for advanced users, which matches what we've already done in Blockly.  Maybe the beginners only get +/-, and the advanced users get chevrons as well.

If we do implement reordering in the same extension instead of having it be an additional extension, that would be a trivial toggle. I guess it'd be something you'd want to save in the mutator DOM.

/s/ Adam

Uwe K

unread,
Mar 28, 2020, 5:37:30 PM3/28/20
to Blockly
On Wednesday, March 25, 2020 at 6:10:40 PM UTC+1, Beka Westberg wrote:
My question is: Do you think each individual input should have a plus and a minus? Just a minus and not a plus? Or do you think the current design is good?


I prefer one plus per block and one minus per input. In general I think +/- is easier for learners then the current mutator system, so I'm happy that you are exploring this. One downside is that +/- buttons add more visual elements to the already busy workspace. Maybe show them only if the block is selected or hovered over?

Adams system sounds good as well. But it always has one unconnected input at the end of each list, right? That's problematic when we teach students to watch out for unconnected inputs. Maybe the list block could offer extra inputs only while a potential input target block is being dragged over it. In the attached image the top block would be the default state of the list block and the bottom one is the "a potential input target is dragged over me" state. When the user connects a target to one of the offered inputs, the other (unconnected) inputs disappear again. There would no mutator UI. Though I'm not sure how feasible it is and if students would "overlook" it.
autoListMutator.png

Beka Westberg

unread,
Mar 28, 2020, 9:17:42 PM3/28/20
to Blockly
Hello peoples,

Thank you for your responses! Based on them, here's an update on my plan:
- Add a minus to each "else if" input on the if block.
- Stick with a static else for now.
- Everything else will stay the same as the original post.

Everyone seems to agree that the 'if' block needs a minus on each input, so I feel good about implementing that. My decisions on the other points are a mixture of what people are saying, and my own personal feelings. So please keep discussing! If you feel strongly about something, the plan could definitely change!

Also note: Blockly team has veto power.

Now I'll respond to some individual little things.

---

At minimum, I would split into two use cases:
- input reordering is required
- input reordering is not required

I like this idea! And I think the input reordering could definitely be useful. But since they're two different cases, I think I'll focus on getting the reordering not-required working first. If you definitely want reordering support I can totally add it though!

...that block can, internally, be an if/elseif/else block without any elseifs attached.

This isn't a response to the point you were making Adam, but I just wanted to note that I actually had to move to this method. Adding a mutator to the controls_ifelse block doesn't work because then the developer can't switch from +/- back to default.

I think I would split apart function blocks from the rest of the problem, because of the connection between mutating a definition and mutating a caller.
I think this isn't necessarily a problem; as long as the callbacks are sufficiently robust it shouldn't need special-cased implementation. On the other hand, I don't see a problem with leaving function blocks with their existing implementation while we focus on the other use cases.

There are actually some things with callers that need to be cleaned up before moving inputs can be implemented. That might have been why Rachel said to set them aside for now. 

And I would split if/else blocks into a set for beginnings and a set for advanced users, which matches what we've already done in Blockly.  Maybe the beginners only get +/-, and the advanced users get chevrons as well.
If we do implement reordering in the same extension instead of having it be an additional extension, that would be a trivial toggle. I guess it'd be something you'd want to save in the mutator DOM.

I'm not sure how you'd want to handle that, because I think adding it to the XML would make it hard to "upgrade" a user to the new blocks. I think we'd have to see how developers would want to use it before making any decisions.


I prefer one plus per block and one minus per input. In general I think +/- is easier for learners then the current mutator system, so I'm happy that you are exploring this. One downside is that +/- buttons add more visual elements to the already busy workspace. Maybe show them only if the block is selected or hovered over?

Thank you for your input! I'm not sure about hiding them unless the block is selected, because I think that might hurt discoverability. For now I'm going to leave them visible by default. Clutter is definitely a thing though! So I think this is a good idea to keep in mind.

Adams system sounds good as well.

A note about Adam's system: I also think it sounds really good! But I know that he could definitely do it better than I could, so I think I'm going to leave that to him if he wants to implement it hehe.

Also I really like your suggested "dragged over" block. I think that pretty much solves the reordering issue! Nice suggestion!

Thank you everyone once again for your input! This has been a really big help!
--Beka 
 
 

Coda Highland

unread,
Mar 29, 2020, 9:57:14 AM3/29/20
to blo...@googlegroups.com
On Sat, Mar 28, 2020 at 8:17 PM Beka Westberg <bekawe...@gmail.com> wrote:
...that block can, internally, be an if/elseif/else block without any elseifs attached.

This isn't a response to the point you were making Adam, but I just wanted to note that I actually had to move to this method. Adding a mutator to the controls_ifelse block doesn't work because then the developer can't switch from +/- back to default.

Why not? It should just be a property on the mutator.
 
I think I would split apart function blocks from the rest of the problem, because of the connection between mutating a definition and mutating a caller.
I think this isn't necessarily a problem; as long as the callbacks are sufficiently robust it shouldn't need special-cased implementation. On the other hand, I don't see a problem with leaving function blocks with their existing implementation while we focus on the other use cases.

There are actually some things with callers that need to be cleaned up before moving inputs can be implemented. That might have been why Rachel said to set them aside for now. 

I wasn't talking about moving inputs, though. Do those things prevent using +/- instead of the existing mutator UI?
 
And I would split if/else blocks into a set for beginnings and a set for advanced users, which matches what we've already done in Blockly.  Maybe the beginners only get +/-, and the advanced users get chevrons as well.
If we do implement reordering in the same extension instead of having it be an additional extension, that would be a trivial toggle. I guess it'd be something you'd want to save in the mutator DOM.

I'm not sure how you'd want to handle that, because I think adding it to the XML would make it hard to "upgrade" a user to the new blocks. I think we'd have to see how developers would want to use it before making any decisions.

I see two ways of going about it.

(1) If we're tracking the inputs by UUID, then we have no choice but to save the UUID list in the XML even if we don't provide reordering.
(2) If we're using some standardized input name (e.g. CONDITION_0, CONDITION_1, CONDITION_2, etc.) then reordering would have to rename the affected inputs. Again we'd have to implement this even if we don't provide reordering if we're going to support putting a - on each entry.

In either case upgrading to reordering would just be a matter of saving a boolean attribute.


I prefer one plus per block and one minus per input. In general I think +/- is easier for learners then the current mutator system, so I'm happy that you are exploring this. One downside is that +/- buttons add more visual elements to the already busy workspace. Maybe show them only if the block is selected or hovered over?

Thank you for your input! I'm not sure about hiding them unless the block is selected, because I think that might hurt discoverability. For now I'm going to leave them visible by default. Clutter is definitely a thing though! So I think this is a good idea to keep in mind.

+1 to discoverability concerns; discoverability is a major reason to be looking into this in the first place. However, one thing that might be worth considering as a means of balancing the options is having them render at 50% opacity until you mouse over the block. This will keep them visible (and prevent the block geometry from changing on mouseover, or prevent large chunks of apparently-empty padding) while not demanding the same level of visual attention, which may help with readability.
 
Adams system sounds good as well.

A note about Adam's system: I also think it sounds really good! But I know that he could definitely do it better than I could, so I think I'm going to leave that to him if he wants to implement it hehe.

I could? I mean, I definitely CAN do it, but better than you?

I seem to have lost my old implementation, but it wasn't particularly difficult. And I'm not nearly as well-versed in the Blockly internals so at the very least any code I produce will need a heavy review to bring it in line with the coding standards.
 
Also I really like your suggested "dragged over" block. I think that pretty much solves the reordering issue! Nice suggestion!

While it's a good suggestion, it's also EXCEPTIONALLY invasive. I can't imagine how to implement this without core support from the renderer, because as it stands there's no way of highlighting the insertion point when dragging over the block. 

The only other alternative I can see would be to go ahead and create the new input on dragover, which will cause a lot of transient updates to the data structures during the drag, which is... less than ideal. It also causes ripple updates to everything south of the block in the UI, which is also not great, especially if that results in the geometry of the dragged-over block changing -- there are some edge cases that could get really chaotic, such as if you have two blocks that support this behavior connected together and you're trying to insert a block at the end of the first one, or if you have a mutable block inside of a mutable block.

/s/ Adam

Coda Highland

unread,
Mar 29, 2020, 9:58:27 AM3/29/20
to blo...@googlegroups.com
On Sun, Mar 29, 2020 at 8:56 AM Coda Highland <chig...@gmail.com> wrote:
On Sat, Mar 28, 2020 at 8:17 PM Beka Westberg <bekawe...@gmail.com> wrote:
I think I would split apart function blocks from the rest of the problem, because of the connection between mutating a definition and mutating a caller.
I think this isn't necessarily a problem; as long as the callbacks are sufficiently robust it shouldn't need special-cased implementation. On the other hand, I don't see a problem with leaving function blocks with their existing implementation while we focus on the other use cases.

There are actually some things with callers that need to be cleaned up before moving inputs can be implemented. That might have been why Rachel said to set them aside for now. 

I wasn't talking about moving inputs, though. Do those things prevent using +/- instead of the existing mutator UI?

Answering my own question: Deleting something from the middle of the parameter list implicitly triggers a reordering even if we don't support arbitrary reordering, so... yes. Yes they do.

/s/ Adam

Beka Westberg

unread,
Mar 30, 2020, 6:42:27 PM3/30/20
to Blockly
I've just put up a PR into the sames repo, so if anyone is interested feel free to take a look! Note that it only includes the code for mutator blocks that already exist in Blockly core. There is no helper API at this time, but hopefully in the future there will be!

On Sunday, March 29, 2020 at 6:57:14 AM UTC-7, Coda Highland wrote:
On Sat, Mar 28, 2020 at 8:17 PM Beka Westberg <bekawe...@gmail.com> wrote:
...that block can, internally, be an if/elseif/else block without any elseifs attached.

This isn't a response to the point you were making Adam, but I just wanted to note that I actually had to move to this method. Adding a mutator to the controls_ifelse block doesn't work because then the developer can't switch from +/- back to default.

Why not? It should just be a property on the mutator.

Sorry I didn't explain this very well. The default controls_ifelse block doesn't have a mutator. So if we give it a mutator via the +/- mutators extension thing, and allow the user to mutate the block, default blockly won't be able to de-serialize those mutations. For this extension I wanted to make sure developers could switch back and forth between the mutator UIs, so the mutator on the controls_ifelse block had to go.

But the same functionality can be achieved with the controls_if block alone, so it's all good :D

--Beka

Coda Highland

unread,
Mar 30, 2020, 6:45:06 PM3/30/20
to blo...@googlegroups.com
On Mon, Mar 30, 2020 at 5:42 PM Beka Westberg <bekawe...@gmail.com> wrote:

Sorry I didn't explain this very well. The default controls_ifelse block doesn't have a mutator. So if we give it a mutator via the +/- mutators extension thing, and allow the user to mutate the block, default blockly won't be able to de-serialize those mutations. For this extension I wanted to make sure developers could switch back and forth between the mutator UIs, so the mutator on the controls_ifelse block had to go.

But the same functionality can be achieved with the controls_if block alone, so it's all good :D


Oh! Okay, that's all good then!

/s/ Adam 

Léo Briand

unread,
Apr 23, 2020, 7:43:33 AM4/23/20
to Blockly
As promised earlier, here is a link to how we implemented the +/- in Vittascience platform: https://fr.vittascience.com/python/?link=5ea17ed78d2c8

Feel free to tell me your opinion on this!

Best,
Léo

Beka Westberg

unread,
Apr 23, 2020, 12:39:27 PM4/23/20
to blo...@googlegroups.com
Thank you for sharing Léo!

I like how you decided to move the +/- buttons for lists from the end of the block to the front. That avoids the annoying chasing I always hate haha.

One thing I never asked: How did you implement your buttons? Are they: icons, an image field subclass, just image fields (i.e. using constructor params), or something else? After some discussion we decided to just use an image field for the plugin (which I think works well!). I'm just curious how you guys decided to tackle this.

P.S. I love your app! Especially that matrix block. Forcing it to be square is a really clever, elegant approach! I kinda really want one now lol

Thank you again I enjoyed getting to take a look :D
--Beka

--
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+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/52f62197-2afe-4273-8124-ce3fc3ab4e57%40googlegroups.com.

Matthew Bishop

unread,
Apr 23, 2020, 1:53:59 PM4/23/20
to Blockly
I find Vittascience's approach really intuitive too. Leo is there a git repo we can all look at?


On Thursday, April 23, 2020 at 9:39:27 AM UTC-7, Beka Westberg wrote:
Thank you for sharing Léo!

I like how you decided to move the +/- buttons for lists from the end of the block to the front. That avoids the annoying chasing I always hate haha.

One thing I never asked: How did you implement your buttons? Are they: icons, an image field subclass, just image fields (i.e. using constructor params), or something else? After some discussion we decided to just use an image field for the plugin (which I think works well!). I'm just curious how you guys decided to tackle this.

P.S. I love your app! Especially that matrix block. Forcing it to be square is a really clever, elegant approach! I kinda really want one now lol

Thank you again I enjoyed getting to take a look :D
--Beka

On Thu, Apr 23, 2020 at 4:43 AM Léo Briand <leobr...@gmail.com> wrote:
As promised earlier, here is a link to how we implemented the +/- in Vittascience platform: https://fr.vittascience.com/python/?link=5ea17ed78d2c8

Feel free to tell me your opinion on this!

Best,
Léo

--
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 blo...@googlegroups.com.

Sam El-Husseini

unread,
Apr 23, 2020, 2:10:29 PM4/23/20
to Blockly
Another approach that instead places the plus / minus at the end of the array block, see the MakeCode editor: https://makecode.microbit.org/_KapfEuATgVHi

Screen Shot 2020-04-23 at 11.07.11 AM.png





Cheers,
Sam

Léo Briand

unread,
Apr 24, 2020, 2:30:00 AM4/24/20
to Blockly
Hi,

Thanks for the feedback. We are mostly reusing code from MakeCode for lists, texts and if/else, and created our own solution for functions.
I find it more convenient to have +/- before the inputs to avoid chasing them when adding or removing elements.

We do not have a public GitHub yet, but I can send you pieces of code if you want.
For instance with the text_join block :
     
{
"type": "text_join",
"output": "String",
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
"style": "text_blocks",
"helpUrl": "%{BKY_TEXT_JOIN_HELPURL}",
"tooltip": "%{BKY_TEXT_JOIN_TOOLTIP}",
"extensions": [
"block_buttons_plus_minus",
"text_join_init"
],
"mutator": "text_join_mutator"
},
// Block for joining text items

I would be happy to share more if you tell me what you would like to see.
Best,
Léo

Reply all
Reply to author
Forward
0 new messages