Databound Editable Attribute

Topics: Developer Forum, Project Management Forum, User Forum
Jan 21, 2009 at 6:21 PM
I want to create a DropDownList Editable Attribute to use when editing a custom ContentItem. The values of the DropDownList must be populated at run time. For example, from a database, xml, list etc.

The example http://n2cms.com/Documentation/Definition/Custom%20editors.aspx is what I am looking for, but I want to assign the datasource at run time not at at design time.

I tried to accomplish this by passing the datasource in through the attribute parameters, but my datasource was a Dictionary and I received the following build error "An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type."

Thanks in advance for your help.

Daniel
Coordinator
Jan 21, 2009 at 6:28 PM
Since the data is only known at runtime I don't see why you need to pass any data source in the attribute's parameters.

The example on that link uses the N2 find API to retrieve items in the tree structure and pass it to the drop down's datasource. You could just replace that with a database or xml query.
Jan 21, 2009 at 8:08 PM
The component needs to be extract and not know what data it is binding too. That should be determinted by the application developer, not the component developer.
Jan 21, 2009 at 10:48 PM
Edited Jan 21, 2009 at 10:49 PM
Daniel, if you want a data-bound drop down to be the editor for N2 content item property, than:

  1. Put your drop-down logic (along with DataSource, DropDownList, whatever) into User Control (.ascx)
  2. Expose an edited value as a public property
  3. Apply your control to your ContentItem's property via [EditableUserControlAttribute]
To illustrate the idea here's how a MyDropDown.ascx user control might look like:

<%@ Control    Language="C#" %>
<script runat="server">
    //A public property with an edited values
    public string SelectedValue {
        get { return this.ddl.SelectedValue; }
        set { this.ddl.SelectedValue = value; }
    }
</script>

<n2:ItemDataSource runat="server" ID="ds" />

<asp:DropDownList
        runat="server"
        AppendDataBoundItems="true"
        DataSourceID="ds"
        DataTextField="Title"
        DataValueField="Name"
        ID="ddl">
    <asp:ListItem Text="(none)" Value="" />
</asp:DropDownList>
And here is how to consume it in your ContentItem:
        ...
        [EditableUserControl(
            "My data-bound drop down list",
            "~/Web/Controls/MyDropDown.ascx",
            "SelectedValue",
            1)]
        public string Test2 {
            get { return this.GetDetail<string>("test2", string.Empty); }
            set { this.SetDetail<string>("test2", value); }
        }
        ...
Jan 22, 2009 at 7:52 PM
Edited Jan 22, 2009 at 7:53 PM
A coworker and I were able to come up with a way to use reflection to find a datasource in the contentItem, which will in effect allow an Editable Atribute be databound during run time. Please take a look at the following code and let me know if you see anything that can be done better. Particularly when we expect the container parameter of the AddEditor function to be a certain type. Also you will notice that we have made it specific to dictionasies which was my goal in the first place, but also made it flexible to handle any datasource.

public class EditablePageDropDownListAttribute : AbstractEditableAttribute
    {
        public string DataMember { get; set; }
        public string DataTextField { get; set; }
        public string DataValueField { get; set; }

        public EditablePageDropDownListAttribute(string title, int order, string dataMember, string dataTextField, string dataValueField)
            : base(title, order)
        {
            this.DataMember = dataMember;
            this.DataTextField = dataTextField;
            this.DataValueField = dataValueField;
        }

        public EditablePageDropDownListAttribute(string title, int order, string dataMember)
            : this(title, order,dataMember, null, null) {}
        
        protected override Control AddEditor(Control container)
        {
            DropDownList rbl = new DropDownList();

            N2.ContentItem currentItem = ((ItemEditor)container.NamingContainer).CurrentItem;

            object listSource = ReflectionUtils.GetPropertyValue(this.DataMember, currentItem);

            IEnumerable enumerableListSource = listSource as IEnumerable;
            if (listSource == null)
                throw new System.InvalidOperationException("Drop down list won't work without values!");
            IDictionary dictionaryListSource = listSource as IDictionary;

            if(dictionaryListSource != null)
            {
                foreach(object key in dictionaryListSource.Keys)
                    rbl.Items.Add(new ListItem(dictionaryListSource[key].ToString(), key.ToString()));
            }
            else
            {
                rbl.DataMember = this.DataMember;
                rbl.DataTextField = this.DataTextField;
                rbl.DataValueField = this.DataValueField;
                rbl.DataBind();
            }

            container.Controls.Add(rbl);
            return rbl;
        }

        public override void UpdateEditor(N2.ContentItem item, Control editor)
        {
            DropDownList rbl = editor as DropDownList;
            rbl.SelectedValue = (item[this.Name] == null) ? rbl.Items[0].Value : item[this.Name] as string;
        }

        public override bool UpdateItem(N2.ContentItem item, Control editor)
        {
            DropDownList rbl = editor as DropDownList;
            item[this.Name] = rbl.SelectedValue;
            return true;
        }
    }