November Happy Hour will be moved to Thursday December 5th.

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.