Skip to content

Conversation

@samelhusseini
Copy link
Contributor

@samelhusseini samelhusseini commented Sep 17, 2019

The basics

  • I branched from develop
  • My pull request is against develop
  • My code follows the style guide

The details

Resolves

#2493
#2926

Proposed Changes

Only generate options during inialization (as we need the first tuple) and when a use clicks on the field to open it. (ie: when the editor is shown / widget is created).

Reason for Changes

Be more efficient about calling a dynamic dropdown function and not break in places where we expect certain options to be available.

Test Coverage

Tested the playground, and added a new test dropdown block that returns a random number of items every time the generate method is called.

Tested on:

  • Desktop Chrome

Documentation

Additional Information

@samelhusseini
Copy link
Contributor Author

@BeksOmega FYI.

I'm also a little surprised that we don't support an empty options list, but that can be dealt with in a different change.

Copy link
Contributor

@BeksOmega BeksOmega left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good! I'm glad you went back and found this one.

tuple);
}
}
if (!options.length) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Uncaught errors are the worst.

* @type {Array.<!Array.<string>>}
* @private
*/
this.generatedOptions_ = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you look into having this be a map/dictionary? I think there are some loops that could be elliminated that way.

Copy link
Contributor Author

@samelhusseini samelhusseini Sep 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would the key of this map be? If it's the value, it means you can't have multiple options with the same value, label is the same. I don't think we set that restrictions right now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would the key of this map be?

It would be keyed by value. This is what the dropdown currently uses, it's just inefficient and uses for-loops instead of a map.

We may not currently throw errors, but the language-neutral keys of your dropdown have to be unique, otherwise your dropdown breaks:

SameValues

Steps to reproduce

  1. Add the following block definition to the playground.
{
    "type": "test_dropdowns_same_value",
    "message0": "same value %1",
    "args0": [
      {
        "type": "field_dropdown",
        "name": "FIELDNAME",
        "options": [
          [ "first item", "ITEM1" ],
          [ "second item", "ITEM2" ],
          [ "third item", "ITEM2" ],
          [ "fourth item", "ITEM2" ],
          [ "fifth item", "ITEM2" ],
        ]
      }
    ],
    "style": "fields_dropdowns"
  }
  1. Add the block to the workspace.
  2. Use the dropdown.
  3. Observe the bad behavior.

If we were to switch it to a map we could definitely get rid of the loop in doClassValidation_. We could also probably get rid of the selectedIndex_ property and just use the value instead, elliminating the loop in doValueUpdate_.

})
};

Blockly.Blocks['test_dropdowns_dynamic_random'] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fun hehe.

@BeksOmega
Copy link
Contributor

I'm also a little surprised that we don't support an empty options list, but that can be dealt with in a different change.

Do you have a use case in mind? I think that's it generally good to oppose that behavior so that you avoid dealing with nulls in code generation. I'm having trouble thinking of a situation where the limitation can't be designed around (e.g. variables), but I could be missing something.

@RoboErikG
Copy link
Contributor

Do you have a use case in mind? I think that's it generally good to oppose that behavior so that you avoid dealing with nulls in code generation. I'm having trouble thinking of a situation where the limitation can't be designed around (e.g. variables), but I could be missing something.

I mentioned to @samelhusseini that I think our APIs made this easy to do with dynamic dropdown functions. Looking at our existing code, an empty options list is allowed by validateOptions_ but then we assume it's non-empty a few lines later in the constructor. It's probably better if we handle an empty list in one spot instead of expecting every option generator to check for it.

@BeksOmega
Copy link
Contributor

It's probably better if we handle an empty list in one spot instead of expecting every option generator to check for it.

I agree that it is a bit annoying to have to design around this limitation (as with most limitations hehe) but I think allowing empty lists and nulls would actually end up creating /more/ work (for us, and outside developers) in the longrun.

Firstly, if outside developers don't handle this at option-generation-time they'll end up having to handle it at code-generation-time. That is a much trickier spot to find bugs. I think we're actually helping them by notifying them before it gets to that point.

Secondly, allowing empty lists and nulls creates a lot more work for us, because now we have to check for that.

We'd have to start checking for empty lists all over the dropdown field, and special case them. For example:

  • Constructor would have to pass something different to the super.
  • getText_ would have to return '' or BLANK or some placeholder.
  • And render_ would have to special-case somehow.

We'd also no longer be able to confidently call field.getValue() in a generic context, because it could return null. Making getValue() non-null saves us a lot of work, because it means you can remove if(field.getValue()) checks.

Two other small points:

  1. That getOptions()[0] line has been around since the dawn of Blockly, and people seem mostly cool with it.
  2. Displaying an empty dropdown is pretty buggy looking behavior anyway. If I were using a block programming language, I'd report that.

In Conclusion:
I think that throwing errors for empty lists is the proper way to handle things.

It saves outside developers the headache in the long run. It makes Blockly less fragile and more elegant. And I can't think of a usecase for empty lists (at least one that doesn't have a better solution).

@RoboErikG
Copy link
Contributor

@BeksOmega good points, especially about it never working with an empty list. If @samelhusseini agrees then I'm okay with requiring a non-empty list of options and updating validateOptions_ to explicitly check for an empty list and throw a meaningful error in that case.

@samelhusseini
Copy link
Contributor Author

I agree with the points made here. This PR adds a check in validateOptions and throws if the list returned is empty. I'll add further code comments so that we're more explicit about this.

* cached options or to re-generate them.
* @return {!Array.<!Array>} A non-empty array of option tuples:
* (human-readable text or image, language-neutral name).
* @throws If generated options are incorrectly structured.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Could we add the type here? @throws {Type}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants