Join us this Friday for AI in Action at the Virtual Happy Hour! This free virtual event is open to all—enroll now on Academy and don’t miss out.

 

SelectOne attribute with IList<string> property

Ted
Ted
Vote:
 

Hi!

Is anyone aware of a limitation in using a SelectOne attribute with an IList<string> property?

The property looks like this:

[SelectOne(SelectionFactoryType = typeof(CountrySelectionFactory))]
public virtual IList<string>? Countries { get; set; }

The idea is to allow the editor to add an arbitrary number of strings, but each string should be selected from the dropdown.

Whenever I click "+" to add a value, I get just an empty space where the textbox would otherwise be:

If I add a string without the SelectOne attribute, and then later add the attribute, the dropdown shows up as expected:

However, if I click "+" to add another value, there's no dropdown (and no textbox) and I get a rather generic Dojo error in the console. 🤔

Edit: I realize this isn't exactly aligned with the original idea behind the SelectOne attribute, but I would think this requirement makes sense?
It makes more sense than trying to apply a SelectionEditor (i.e. dropdown) to the entire property?

I guess what I'm looking for is how to change the editor for each item in the IList<T> property, without having to roll my own.

#304605
Edited, Jul 04, 2023 10:23
Vote:
 

I don't think the SelectOne property was designed to be used like that. If the list of items is finite, have you considered just using the SelectMany attribute on a string property instead?  It will end up as a comma separated list that you could convert into a list at consumption time.

[SelectMany(SelectionFactoryType = typeof(CountrySelectionFactory))]
public virtual string? Countries { get; set; }

[ScaffoldColumn(false)]
public IList<string>? SelectedCountries => Countries?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)?.ToList();
#304606
Edited, Jul 04, 2023 10:57
Ted - Jul 04, 2023 11:07
I did try it, but it's not really feasible for UX reasons since there are 200+ options in the selection factory, and the block type itself is used in a IList property.

When expanding one of the "items" (i.e. blocks) the list of countries takes up *huge* vertical space. :)
Ted
Vote:
 

Didn't find an easy fix, but I got it to work with a custom editor based on CheckBoxListEditor as that one seemed to work pretty much out of the box when using SelectMany on an IList<string> property:

Not saying it's the way to go, but here's the quick-and-dirty editor I used to get the job done:

define([
  "dojo/_base/array",
  "dojo/_base/declare",
  "dojo/_base/lang",
  "dojo/on",
  "dijit/form/Select",
  "epi-cms/contentediting/editors/CheckBoxListEditor"
], function (
  array,
  declare,
  lang,
  on,
  Select,
  CheckBoxListEditor
) {

  return declare([CheckBoxListEditor], {

    buildRendering: function () {

      this.inherited(arguments);

      // Create a dropdown for the values passed from the selection factory
      const select = new Select({
        name: "string-select",
        options: array.map(this.selections, (item) => {
          return { label: item.text, value: item.value, selected: item.value === this.value }
        })
      }).placeAt(this.domNode);

      // Update property value when dropdown value changes
      this.own(
        on(select, "change", lang.hitch(this, function (newValue) {
          this._set("value", newValue);
          this.onChange(this.value);
        }))
      );
    },

    _addCheckBoxForItem: function () {
      // Do nothing, we add a Select instead of checkboxes
    }

  });
});

And here's the editor descriptor:

public class CountriesEditorDescriptor : EditorDescriptor
{
    public const string UIHint = "Countries";

    public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
    {
        base.ModifyMetadata(metadata, attributes);

        metadata.EditorConfiguration["valueIsCsv"] = false;

        metadata.ClientEditingClass = "my-site/StringListSelectionEditor";

        metadata.SelectionFactoryType = typeof(CountrySelectionFactory);
    }
}
#304613
Edited, Jul 04, 2023 14:00
Vote:
 

Nicely done Ted, feels like a great candidate for the blogs section :)

#304614
Jul 04, 2023 14:54
Vote:
 

Hi Ted,

You could achieve this without doing the dojo bit and using a generic list:

        [EditorDescriptor(EditorDescriptorType = typeof(CollectionEditorDescriptor<CustomCountries>))]
        public virtual IList<Countries> Countries { get; set; }
    }

    public class Countries
    {
        [Display(
           GroupName = GroupNames.Content,
           Order = 10)]
        [SelectOne(SelectionFactoryType = typeof(CountriesSelectionFactory))]
        public virtual string Country { get; set; }
    }

    [PropertyDefinitionTypePlugIn]
    public class CountriesProperty : PropertyList<CustomCountries> { }

Thanks

Paul

#304615
Jul 04, 2023 16:19
Ted - Jul 04, 2023 16:41
Good point, but I wanted to do it without introducing another property type.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.