views:

378

answers:

7

I'm working with a 3rd party system to implement some forms in a website.

The 3rd party system provides me with XML definitions for these forms. e.g.

<form>
    <segment>
        <label>The header</label>
        <fields>
            ...
            <field>
                <id>field_Dob</id>
                <type>Date</type>
                <label>Date of Birth</label>
                <required>1</required>
            </field>
            ...
        </fields>
    </segment>
    ...
</form>

I am parsing this XML in a Server Control and programatically generating a tree of controls. The labels of the controls are passed through in the XML.

It is part of our proposal to "inject" little help texts into this form.

Ideally I would like to pass these help texts in from the markup of the top level control so that non-developers (HTML monkies) can change the help texts, and associate them with the field by it's ID. Something like so

<controls:MyCrazyForm runat="server">
    <helpTexts>
        <helpText for="field_Dob">
Some rambling nonsense to do with the DOB field
        </helpText>
        ...
    </helpTexts>
</controls:MyCrazyForm>

The controls are parsed recursively.

The Form creates a fieldset for each segment, fieldsets create many FieldXXX (where XXX = date, text, combobox etc) depending on the data type.

The FieldXXX types create a div and then several standard .net controls (TextBox, DropDownList, etc) to actually render themselves. It is at this point, within the containing div that I need to output the help text.

My Question

What is the "best" way to get these texts from the top-level form control to these child controls which are 3 or 4 levels deeper in the control tree.

There will only ever be one of these forms on a page. Should I make the top level form as Singleton and get it like so...?

if(MyCrazyForm.Instance.HelpTexts.ContainsKey("theIdOfTheCurrentField"))
{
    this.HelpText = MyCrazyForm.Instance.HelpTexts["theIdOfTheCurrentField"];
}

Should I pass a reference to the form into every control all the way down the tree (this seems messy)?

Am I miles of target with my architecture of this (although it's working realyl nicely at the moment) form and should I look at a different method of implementation?

Thanks

A: 

What about traversing upwards the parents of the control until you find a control of the certain type, or with a specific member variable called "HelpTexts"?

This is a dynamic programming approach, but for certain infrastructural elements of design, as long as such approaches are limited, as well as being well-commented as to why that design approach was taken, I'd find it acceptable.

This would be to use the reflection APIs, and a generalised static helper function, such as

GetHelpTextFromParent(Control controlParent, string id)

This would then inspect the controlParent to see if it had the member variable HelpTexts within it, and then look up by id the helptext. If not, it would recursively call GetHelpTextFromParent with the parent of controlParent, until a suitable terminating condition. (I mention "suitable terminating condition" because it is probably suboptimal to keep this up until you get to the desktop window, you may want to terminate the recursion prior to then, but this would be a testing/debugging issue.)

polyglot
A: 

If you're parsing this XML in a Server Control and programatically generating a tree of controls how do you make sure the HTML guys keep those IDs in sync? If they have access to those XMLs maybe you should let them add helpTexts right there and not on the aspx.

But to answer your question, I suppose you're parsing the xml after the aspx markup was read and built so you could index those texts right before parsing the XML. Then when you actually build your dynamic control tree you look up your index by the control's id defined in you source xml and hook it up at this point.

EDIT: Okay, depending on how you build those child controls, I'd expose all of those inline helptexts of the form internally as a property indexed by the control id which would give you the help text or an empty string. I'm not sure I get the MyCrazyForm.Instance though.

The XML is supplied by a 3rd party application. It is not possible to amend the XML within any reasonable timescale. This is why I wish to augment it from the ASPX end
Greg B
Okay, now I see you're including those texts inside your custom form. So you should handle XML parsing and HelpText parsing inside your custom form control. Like I said, parse your texts, then parse the 3rd party XML and combine them.
Hi runtime. I get that, and am currently doing that. My question is what is the "bets" (easies, cleanest, most flexible?) way of getting the data that is parsed at the top level (in the control in the page) down through 3 or 4 levels of controls to where it is needed.
Greg B
A: 

I suggest to create an interface like this

interface IHelpTextProvider
{
    Dictionary<string, string> HelpTexts
    {
        get;
    }
}

Then your form control can implement this interface and pass a reference to this interface when creating fieldsets and FieldXXX controls.

Alternatively, you can do as polyglot suggested and when help text is needed, FieldXXX control go recursively through its parents until a parent implementing IHelpTextProvider interface is found.

Juozas Kontvainis
A: 

You could create an event in the server control that it would be raised whenever it wants/needs a help text for a given field. The form can hook up an event handler and respond to it. That way you don't need to pass around some object to give the server control access to the information. For this we need to do three things.

Create an EventArgs class:

class HelpTextEventArgs : EventArgs
{
    public string Text { get; set; }
    public string FieldId { get; private set; }
    public HelpTextEventArgs(string fieldId)
    {
        FieldId = fieldId;
    }
}

Create the event in the server control:

public event EventHandler<HelpTextEventArgs> HelpTextRequested;

protected void OnHelpTextRequested(HelpTextEventArgs e)
{
    EventHandler<HelpTextEventArgs> evt = this.HelpTextRequested;
    if (evt != null)
    {
        evt(this, e);
    }
}
// wrapper for the event raising method for easier access in the code
public string GetHelpText(string fieldId)
{
    HelpTextEventArgs e = new HelpTextEventArgs(fieldId);
    OnHelpTextRequested(e);
    return e.Text;
}

...and set up an event handler in the form that can access the help texts:

private void Page_Load(object sender, EventArgs e)
{
    ServerControl.HelpTextRequested += ServerControl_HelpTextRequested;
}

private void ServerControl_HelpTextRequested(object sender, HelpTextEventArgs e)
{
    e.Text = FindHelpText(e.FieldId);
}

Using this approach the code will work fine even if the hosting form does not provide the help text service; the server control is not depending on the fact that there is an event handler attached.

Fredrik Mörk
+1  A: 

As well as looking at different ways of passing information between controls, as the other answers here have put forward, I think that a different approach may be of value, depending on the specifics of your case. An analogous problem to the one you describe - associating some specific text with some controls on a form - has been solved for the more general case of internationalization, with Resources. I know this is different from the other answers and not directly what you asked in your question, but Resources seem to meet the needs quite well, as described below. Rather than answer your specific question about information flow between controls, I'm trying to consider the end result you're trying to achieve. Go easy on me if I've misunderstood anything :-)

  1. Each form and field within a form have a unique identity. Hence, a resource ID can be constructed uniquely from the form and field.
  2. A resource source file is just XML, completely separate from the specifics of the UI and can be given over to non-developers to fill up with the relevant help text. If you change the UI, this file need not change at all.
  3. At render time, you can just get the resource for a field in a form using its resource ID and incorporate the text in the UI any how you want.
  4. As the same approach is used for I18N/L10N, it is well documented, well understood, declarative, simple and performant.
Vinay Sajip
+1  A: 

It may be more complicated at first, but makes it easier to maintain, why not run the xml file through an xsl procesor? The xslt file would assign the helptext nodes of your helptexts file to the corresponding field nodes.

 <?xml version="1.0" encoding="ISO-8859-1"?>
<form>
    <segment>
        <label>The header</label>
        <fields>
            <field>
                <id>field_name</id>
                <type>string</type>
                <label>Name</label>
                <required>1</required>
            </field>
            <field>
                <id>field_Dob</id>
                <type>Date</type>
                <label>Date of Birth</label>
                <required>1</required>
            </field>
        </fields>
    </segment>
</form>

XSLT file:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>

  <xsl:template match="/form/segment/fields/field[id='field_name']">
    <xsl:copy>
      <xsl:element name="helptext">This is a Name helptext.</xsl:element> 
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="/form/segment/fields/field[id='field_Dob']">
    <xsl:copy>
      <xsl:element name="helptext">This is a Date of birth helptext.</xsl:element> 
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>

    <xsl:template match="node() | text()">
     <xsl:copy>
      <xsl:copy-of select="@*"/>
      <xsl:apply-templates/>
     </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

yields this:

<form>
    <segment>
        <label>The header</label>
        <fields>
            <field>
<helptext>This is a Name helptext.</helptext>
                <id>field_name</id>
                <type>string</type>
                <label>Name</label>
                <required>1</required>
            </field>
            <field>
<helptext>This is a Date of birth helptext.</helptext>
                <id>field_Dob</id>
                <type>Date</type>
                <label>Date of Birth</label>
                <required>1</required>
            </field>
        </fields>
    </segment>
</form>

This xml file can now be parsed like before, but now you can get the help text at the same time as you are generating the form elements. Your HTML monkies then only need to edit the XSLT file, or you simply include another file :

  <xsl:template match="/form/segment/fields/field[id='field_Dob']">
    <xsl:copy>
      <xsl:element name="helptext">
        <xsl:copy-of select="document('field_Dob.txt')"/> 
      </xsl:element> 
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>

You can try out XSL online here

Ozan
Thanks Ozan. Your solution seems the cleanest to me. It enables me keep everything pretty much "as is" and inject the help text into my main XML doc. good work!
Greg B
A: 

As Ozam suggested, you could use XSL.

A separate XML file with the similar structure as that of the 3rd party xml file which contains helpText per XML node would be good & you might need to merge(?) them in some way.

I dont know if this help in any way, at all.

shahkalpesh