MVC XForm container controls - XForm, Group and Repeat

Available for download via the MVC XForms codeplex project. The project includes all source code and a test project that requires no database or external setup. Just download, open up visual studio and press play...

Planned posts in this series:

  1. Introduction to MVC XForms and basic form controls
  2. XForm container controls - Group and Repeat

  3. Automatic validation and deserialization

  4. Extending the framework - Overriding formatting, custom user-controls and Mvc.XForms.UI

  5. JavaScript API and Event Model

Group

XForms spec: "The group element is used as a container for defining a hierarchy of form controls. Groups can be nested to create complex hierarchies."

This means that Group essentially gives you a "context" to work from. An example could be an address of a person:

<% form.Group(p => p.Address).Template(address => { %>
	<%= address.Input(a => a.Address1).Label("Address") %>
	<%= address.Input(a => a.Address2).Label("") %>
	<%= address.Select1(a => Country, ViewData.Model.Countries, c => c.ID, c => c.Name) %>
<% }); %>

Which would generate the following HTML:

<div id="Address" class="xf group">
	<div class="xf input text">
		<label for="Address_Address1">Address</label>
		<input id="Address_Address1" type="text" value="" name="Address_Address1"/>
	</div>
	<div class="xf input text">
		<input id="Address_Address2" type="text" value="" name="Address_Address2"/>
	</div>
	<div class="xf select1">
		<label for="Address_Country_ID">Country</label>
		<select id="Address_Country_ID" name="Address_Country_ID">
			<option value="1" selected="selected">UK</option>
			<option value="2">USA</option>
			<option value="3">France</option>
			<option value="4">Italy</option>
		</select>
	</div>
</div>

The HTML element rendered for the group is "div" by default. This can be changed using the Tag fluent setting. For instance, you might want to do:

<% form.Group(c => c.Address).Tag("fieldset").Template(address => { %>

The beauty of the Group control is that it nicely encapsulates form controls for child objects of the main form model object. It's the perfect control to use for creating reusable components. Say you have a "Link" object that has properties "Name" and "Url". It's likely that this will be reused over different objects and forms. It's fairly straight forward to add an extension method using the Mvc XForms framework that will encapsulate this:

public static class LinkExtensions {
	public static void Link<TModel, TModelContext>(this IContainerControl<TModel, TModelContext> parent, Expression<Func<TModelContext, Link>> bind) {
		var link = new XFormGroup<TModel, TModelContext, Link>(parent, bind);
		link.CssClass.Add("link");

		var response = link.XForm.HttpContext.Response;

		link.Settings.ItemTemplate(x => {
			response .Write(x.Input(l => l.Name));
			response .Write(x.Input(l => l.Url).Label("URL"));
		});
	}
}

Now we can reuse this control wherever an object has a "Link" child object by simply using the following:

<% form.Link(x => x.RelatedLink); %>

All "container controls" (Group, Repeat and XForm) implement the IContainerControl<TModel, TModelContext> interface (the generic arguments here are representing the parent > child relationship). This means that any custom controls you create will appear on intellisense and will be type-safe.

A more advanced way of creating a user control so that you can define your own fluent settings will be discussed in a future post.

Repeat

The XForms Repeat module is the most powerful control in the XForm spec and has no direct correlation with HTML forms. It's not simply a repeating structure in the same sense as the web forms repeater control; it defines a template of XForm controls for displaying and editing collections of objects, such as a list of products in an order. It is most commonly used with triggers to enable inserting and deleting of repeater items (which all happens client-side - no post backs).

The best way to show the power of the repeat is with an example. Say you have a Company object with a list of links:

<%= form.Trigger("AddLink").Label("Add Link")
	.Insert(i => i.Nodeset(c => c.Links).At(At.Last))%>
<% form.Repeat(c => c.Links).ItemTemplate(link => { %>
	<%= link.Input(l => l.Name) %>
	<%= link.Input(l => l.Url) %>
	<%= link.Trigger("Del").Label("x").Delete() %>
<% }); %>

This generates the following HTML (given we have one link in the model):

<div class="xf trigger">
	<input id="AddLink" type="button" value="Add Link"/>
</div>
<ul id="Links" class="xf repeat">
	<li>
		<div class="xf input text">
			<label for="Links-0_Name">Name</label>
			<input id="Links-0_Name" type="text" name="Links-0_Name" value="MVC XForms"/>
		</div>
		<div class="xf input text">
			<label for="Links-0_Url">Url</label>
			<input id="Links-0_Url" type="text" name="Links-0_Url" value="http://www.codeplex.com/mvcxforms"/>
		</div>
		<div class="xf trigger">
			<input id="Links-0_Del" type="button" value="x"/>
		</div>
	</li>
</ul>

This is all the code that is required to have a fully functioning repeat with client-side inserting and deleting.

The trigger has an insert action that defines which repeat to insert a new item into (the "Nodeset" setting) and where in the list to insert it (the "At" setting). The repeat itself has an item template with two inputs and a delete trigger. The default setting for insert and delete actions on repeaters is to delete the current item. Some of the fluent settings for repeat include:

  • Tag: The html tag for the repeat container. By default this is "ul".
  • ItemTag: The html tag for the item containers . By default this is "li". A common scenario might be to use Tag("table").ItemTag("tr") to immediately switch from using an unordered list to a table.
  • DefaultItem: This can be used to set the properties of a newly inserted item.

An example of using the repeat fluent settings:

<%= form.Trigger("AddLink").Label("Add Link")
	.Insert(i => i.Nodeset(c => c.Links).At(At.Last))%>
<% form.Repeat(c => c.Links)
	.Tag("table")
	.ItemTag("tr")
	.DefaultItem(new Link { Name = "Enter link name", Url = "http://" } )
	.ItemTemplate(link => { %>
	<td><%= link.Input(l => l.Name) %></td>
	<td><%= link.Input(l => l.Url) %></td>
	<td><%= link.Trigger("Del").Label("x").Delete() %></td>
<% }); %>

A (small) javascript file is included in the project that facilitates the repeat functionality. Mvc XForms will work without any javascript, but actions, triggers and repeats all require script to allow the client-side interactivity with no post-back.

The way the repeat inserting works is that a json object is built to represent the repeat. Properties of the json include a template that is the HTML required for inserting a new item and any events that are bound to child controls. When an item is inserted, the template is inserted into the DOM, the IDs of the child controls are updated and any events are re-bound to the child controls. Events are only re-bound if they have been bound in the first place using the Action fluent settings on a control. An API of sorts exists in the script that enables javascript functions to be bound to insert and delete actions for further control over functionality. This will probably be the subject of a future post.

XForm

The XForm is the parent element and synonymous with an HTML form. The main parts of an XForm are the Model and Controls. An XForms Model consists of Instances, Submissions and Binds.

Instances

In XForms, these are XML data documents that the form controls bind to. This is what gets submitted to the server. In MVC XForms, the model is moved from the client to the server and CLR objects used instead of XML documents (although in the future there will hopefully be more support for "proper" XForms, including XML instances). It is possible to have multiple instances in XForms, but in MVC XForms, we have one instance for simplicity. This is defined as follows:

<% Html.XForm(ViewData.Model.Customer).Form(form => { %>

This outputs the HTML form tag and gives the XForm its "context". The Form method is a lambda which describes the XForm.

Submissions

These define where and how the data for the XForm is submitted. Multiple submissions are allowed per XForm for defining multiple submission targets. XForms has a much richer submissions module than HTML, but MVC XForms (at the moment) just uses the normal submission methods that HTML allows. The fluent settings for the XForm include:

  • Action: The URL for the XForm submission.
  • Method: The submission method.

For example, to POST form data including file uploads, the following might be used:

<% Html.XForm(ViewData.Model.Customer)
	.Action("Customer", "Edit")
	.Method(SubmissionMethod.FormDataPost)
	.Form(form => { %>

Binds

Binds are constraints on the model. Each bind points to a part of the instance and model item properties are then applied to the instance nodes. This allows various data constraints and manipulation. For example, you might want a property on the model to be required and have a length greater than 6 characters. This would be expressed in XForms as:

<bind nodeset="/customer/name" required="true()" constraint="string-length(.) > 6"/>

Currently MVC XForms does not support this functionality, for the time being you must use the ModelValidator. This is scheduled to be introduced in Release 2 and will be implemented via both attributes and a lambda API.

For more information see http://www.w3.org/TR/xforms11/#concepts-model

blog comments powered by Disqus

About the author

Something about the author

Month List

Page List