Testing the MForm navigation with the Navigation Toolbox
The MForm Toolbox that is placed on pages with MForm available has a special form called "Navigation Toolbox".
The navigation toolbox allows providing a xpath expression and an action that should be performed on the found controls.
This is an easy way to learn MForm XPath subset.
You will notice that the Navigation toolbox is also a MForm control. To see how it is implemented, see ~/Toolbox/MFormToolbox.ascx user control.
In order to enable the Navigation toolbox, the checkbox below must be enabled.
Navigating through MForm controls
Because MForm controls can be repeatedly generated using the MinOccurs
and MaxOccurs properties, it is hard to relate to the inner MForm controls
programmatically using only their IDs.
To resolve this problem, a language for navigating through the controls has been
prepared. This language is a subset of the XML Path Language
(XPath), taking advantage of the fact that MForm controls correspond
to XML nodes.
- Example 1
-
Using this language, it is very easy to find for example all MForm controls, whose
local name equals "SomeControl".
The path would look like this: //*:SomeControl
The MForm XPath abbreviated syntax uses the location path
construct. A location path may consist of one or more location steps.
The navigation starts from one MForm control (usually a root, but can be started
from any MForm control), this control is queried with the first location step from
the location path resulting in an enumeration of MForm controls. All of the MForm
controls in this enumeration are then queried with the next location step and so
on until the last location step, which returns the output result of the whole location
path.
- Example 2
-
Assume that we start the navigation from an MForm control that has a child MForm
control called "Parent". This control has a child MForm control called "ItsChild",
and subsequently this control has a child MForm control called "TheGrandChild".
The path that directs to the last control would look like this: Parent/ItsChild/TheGrandChild
A single location step has three parts:
- an axis, which specifies the tree relationship between the nodes selected by the
location step and the context node,
- a node test, which specifies the node type and expanded-name of the nodes selected
by the location step, and
- zero or more predicates, which use arbitrary expressions to further refine the set
of nodes selected by the location step.
The MForm XPath abbreviated syntax allows 4 axes:
- the child axis -
/
- the descendant or self axis -
//
- the self axis -
.
- the parent axis -
..
In case of child axis and descendant or self axis, using the node test is required.
In case of the other two axes (the self axis and the parent axis), using the node
test is prohibited.
- Example 3
-
When looking for all MForm controls, that have a "SomeControl" as child, you can
use this path:
//SomeControl/..
- Example 4
-
Assume that we are in a MForm control that has a child named "Owner".
The path Owner//. will return the "Owner" control, its children, the
children of its children and so on.
The node test checks the name and type of the control.
The MForm XPath abbreviated syntax allows the following node tests:
- the element node test -
ElementName or prefix:ElementName
- the attribute node test -
@AttributeName
- the all nodes test -
*
- the all nodes in namespace test -
prefix:*
- the all nodes with local name test -
*:LocalName
- the all attributes test -
@*
- Example 5
-
When looking for all MForm controls representing attributes, whose parent's local
name is "AttributeOwner", use the following xpath: use this path:
//*:AttributeOwner/@*
- Example 6
-
Assuming that we have a Choice MForm control named "Choice" located somewhere in
the tree and we want to find all its children (all the actual choices).
The proper XPath would be: //Choice/*
The predicates in XPath are noted in square brackets - [ ].
The implementation of predicates in MForm XPath is currently rudimentary. The only
predicate that MForm supports is the index predicate
If some MForm control has several instances under one parent, they can be disguised
using the index predicate. The index of the first control is
1.
- Example 7
-
To find the first instance of the "Child" MForm control, use the following xpath:
Child[1]
The compound location step
MForm XPath implementation provides a possibility to use unions in location steps.
The union is noted as a | between single location steps. All location
steps constituting the union should be inside round brackets:
(SingleLocationStep | OtherSingleLocationStep | EvenDifferentLocationStep)
The unions may be used only between location steps, not between parts of location
paths.
- Example 8
-
To find all attribute controls and the "AlsoMe" element control of the "Parent"
control, located under the control that we run the path, use the following xpath:
Parent/(@* | AlsoMe)
- Example 9
-
To find all controls whose parents are called "Mommy" or "Daddy", use the following
xpath:
//(Mommy | Daddy)/*
Using MForm XPath navigation
MForm Xpath navigation should be simple and intuitive for all the developers that
already know XPath. Besides few exceptions (described in the text above) the Xpath
query run on the controls would also work as the XPath query run on the MForm output
XML.
The reason for creating the MForm XPath navigation language is actually creating
such a simple an intuitive tool for querying MForm controls, not implementing the
whole XPath functionality. Probably the MForm Xpath subset will be broadened a bit
but only for consistency reasons (i.e. the descendant-or-self axis is implemented
but the ancestor-or self axis is not).
It is worth mentioning that an output of the MForm XPath Select extension
method is an IEnumerable<OffspringItem> controls, which further
can be refined using the LINQ query (this is the most important reason
why predicates are barely implemented in MForm, they can simply be done with LINQ).
- Example 8
-
Assume that we have a "sampleRoot" MForm Root control and we want to find all the item
controls with a value equal "true" and are the children of the "Flags" MForm control.
This code will not work:
sampleRoot.Select("//Flags/*[. = 'true']")
because value predicate is not implemented with MForm XPath. However, the same thing can be implemented like this:
sampleRoot.Select("//Flags/*").Leafs().Where(leafItem => leafItem.Value == "true")
(Leafs() is an extension method that does exactly the same as OfType<LeafItem>())
Of course the second query is longer, but on the other hand all steps are easier
to follow (first take only leaf items, because only them can have values, then check
the values) and what is more important, we get the actual control and can check
all of its properties (also having intellisense checking, etc.).
- Example 9
-
LINQ and MForm XPath can be combined in a manner where there is more
than one XPath query.
Let's assume that we have a "clientsRoot" MForm Root control for providing many clients data. This
control may have the following tree structure:
- Clients
- Client (1 to many)
- FirstName
- Surname
- Adresses
Now we want to find all the "Client" controls, that represent Mr. Jones, whose all
addresses are in the city of Jonestown.
clientsRoot.Select("//Client").Where(
client =>
client.Select("Surname").Leafs().Where(surname => surname.Value == "Jones")
&&
client.Select("Addresses/Address/City").Leafs().All(city => city.Value == "Jonestown")
);