Creating form controls
Creating a Complex Form Control
The following example will show you a Form Control with an auto-updating UI, the UI that changes when the user changes some selection in it. It is a complex control (“Customer”) consisting of two drop down lists. Selecting an item in one dropdown (“Person” or “Organization”), changes the list of items in the other one (listing people or organizations respectively).
It also shows how a Form Control can bind to multiple fields of a data type. The data type has two fields of the data reference type. Each field references a specific data type (“Person” and “Organization”). The Form Control replaces two widgets assigned to those two fields by default (KeySelector).
Please note that for the simplicity of the example, the data sources for both fields are generated on-the-fly in the code behind the control but can be easily replaced with actual items from the corresponding data types.
The steps of adding and using this Form Control are the same as in the previous example. You should:
- Create your control (see the examples below)
- Register the control in Composite.config within the namespace you use for your Form Controls
- Add the control to the data type’s form markup
MyControl.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="Controls_MyControl" %> <%@ Register TagPrefix="aspui" Namespace="Composite.Core.WebClient.UiControlLib" Assembly="Composite" %> <!-- selecting a type - changes to this will update the list of items --> <aspui:Selector ID="typeSelector" runat="server" AutoPostBack="true"> <asp:ListItem Text="Person" Value="Person"></asp:ListItem> <asp:ListItem Text="Organization" Value="Organization"></asp:ListItem> </aspui:Selector> <!-- the selector holding items of the selected type --> <aspui:Selector ID="itemSelector" DataTextField="value" DataValueField="Key" runat="server"> </aspui:Selector>
In the above example, the C1 CMS’s Selector control is used for the drop down lists.
MyControl.ascx.cs
using System; using System.Collections.Generic; using Composite.C1Console.Forms; using Composite.Plugins.Forms.WebChannel.UiControlFactories; public partial class Controls_MyControl : UserControlBasedUiControl, IValidatingUiControl { #region Dummy Data - this could easily be dynamically loaded from the data layer public Dictionary<Guid, string> PersonSource = new Dictionary<Guid, string>() { {Guid.Empty, "select a person ..."}, {Guid.Parse("9bdb7b6d-4e4f-41c9-bcc3-95b20bd8d791"), "Person 1"}, {Guid.Parse("9bdb7b6d-4e4f-41c9-bcc3-95b20bd8d792"), "Person 2"}, {Guid.Parse("9bdb7b6d-4e4f-41c9-bcc3-95b20bd8d793"), "Person 3"}, {Guid.Parse("9bdb7b6d-4e4f-41c9-bcc3-95b20bd8d794"), "Person 4"}, {Guid.Parse("9bdb7b6d-4e4f-41c9-bcc3-95b20bd8d795"), "Person 5"} }; public Dictionary<Guid, string> OrganizationSource = new Dictionary<Guid, string>() { {Guid.Empty, "select an organization ..."}, {Guid.Parse("4bdb7b6d-4e4f-41c9-bcc3-95b20bd8d791"), "Organization 1"}, {Guid.Parse("4bdb7b6d-4e4f-41c9-bcc3-95b20bd8d792"), "Organization 2"}, {Guid.Parse("4bdb7b6d-4e4f-41c9-bcc3-95b20bd8d793"), "Organization 3"}, {Guid.Parse("4bdb7b6d-4e4f-41c9-bcc3-95b20bd8d794"), "Organization 4"}, {Guid.Parse("4bdb7b6d-4e4f-41c9-bcc3-95b20bd8d795"), "Organization 5"} }; #endregion #region Form Properties [FormsProperty()] [BindableProperty()] public Nullable<Guid> PersonGuid { get; set; } [FormsProperty()] [BindableProperty()] public Nullable<Guid> OrganizationGuid { get; set; } #endregion protected void Page_Load(object sender, EventArgs e) { // attach event to the typeSelector typeSelector.SelectedIndexChanged += new EventHandler(typeSelector_SelectedIndexChanged); } #region UserControlBasedUiControl // this is called when the control is initialized - we should grab values from our 'BindableProperty' fields above and show it on our ASP.NET controls. public override void BindStateToControlProperties() { Guid selectedValue = Guid.Parse(itemSelector.SelectedValue); if (selectedValue != Guid.Empty) { switch (typeSelector.SelectedValue) { case "Person": this.PersonGuid = selectedValue; this.OrganizationGuid = null; break; case "Organization": this.OrganizationGuid = selectedValue; this.PersonGuid = null; break; } } else { this.PersonGuid = this.OrganizationGuid = null; } } public override void InitializeViewState() { typeSelector.SelectedValue = this.PersonGuid != null ? "Person" : "Organization"; FillDropDown(); itemSelector.SelectedValue = this.PersonGuid != null ? this.PersonGuid.ToString() : this.OrganizationGuid.ToString(); } #endregion #region IValidatingUiControl // If any selections are invalid, we should return false here. We get called for each field we bind to. public bool IsValid { get { return (this.PersonGuid != null || this.OrganizationGuid != null); } } public string ValidationError { get { return "Missing value.\n"; } } #endregion #region Private methods void typeSelector_SelectedIndexChanged(object sender, EventArgs e) { FillDropDown(); } private void FillDropDown() { switch (typeSelector.SelectedValue) { case "Person": itemSelector.DataSource = PersonSource; itemSelector.DataBind(); break; case "Organization": itemSelector.DataSource = OrganizationSource; itemSelector.DataBind(); break; } } #endregion }
In the above example, dummy data sources are built in the code and used to fill the drop down lists with items. In an actual situation, you will get items from the corresponding data types in C1 CMS.
Please note that on Composite C1 (now C1 CMS) version 3.2 or later, you don’t need to implement the IValidatingUiControl interface for user input validation and can use the validation features out-of-the-box.
In Composite.config, you should add register the control (“MyControl”) under Composite.C1Console.Forms.Plugins.UiControlFactoryConfiguration/Channels/Channel[@name="AspNet.Management"]/Namespaces/Namespace[@name=”{your custom namespace for form controls}”]/Factories:
<Namespace name="http://www.contoso.com/ns/Composite/Forms/1.0"> <Factories> <add userControlVirtualPath="~/Controls/FormControls/MyControl.ascx" name="MyControl" cacheCompiledUserControlType="false" type="Composite.Plugins.Forms.WebChannel.UiControlFactories.UserControlBasedUiControlFactory, Composite" /> </Factories> </Namespace>
Make sure you register it under the correct namespace, with the correct name and the correct path to the control.
If you have not registered a namespace for your form controls yet, add it in Composite.config under Composite.C1Console.Forms.Plugins.ProducerMediatorConfiguration/Mediators:
<add type="Composite.C1Console.Forms.StandardProducerMediators.UiControlProducerMediator, Composite" name="http://www.contoso.com/ns/Composite/Forms/1.0" />
Please note that you need to register a control very time you create a new one, while registering a namespace for your controls is one-time operation provided that all the controls belong to the same namespace. You will need to register a namespace again only if you need another one.
In the data type’s form markup, you should replace the widgets used for the respective data reference fields (“Person” and “Organization” in the example) with your form control (“MyControl”).
<cms:formdefinition xmlns:cms="http://www.composite.net/ns/management/bindingforms/1.0" xmlns="http://www.composite.net/ns/management/bindingforms/std.ui.controls.lib/1.0" xmlns:ff="http://www.composite.net/ns/management/bindingforms/std.function.lib/1.0" xmlns:f="http://www.composite.net/ns/function/1.0"> <cms:bindings> <cms:binding name="Id" type="System.Guid" optional="true" /> <cms:binding name="Title" type="System.String" optional="true" /> <cms:binding name="Person" type="System.Nullable`1[System.Guid]" optional="true" /> <cms:binding name="Organization" type="System.Nullable`1[System.Guid]" optional="true" /> </cms:bindings> <cms:layout> <cms:layout.label> <cms:read source="Title" /> </cms:layout.label> <FieldGroup> <TextBox Label="Title" Help="" SpellCheck="true"> <TextBox.Text> <cms:bind source="Title" /> </TextBox.Text> </TextBox> <MyControl Label="Customer" xmlns="http://www.contoso.com/ns/Composite/Forms/1.0"> <MyControl.PersonGuid> <cms:bind source="Person" /> </MyControl.PersonGuid> <MyControl.OrganizationGuid> <cms:bind source="Organization" /> </MyControl.OrganizationGuid> </MyControl> </FieldGroup> </cms:layout> </cms:formdefinition>