Click or drag to resize
How To implement a custom control

User data is displayed in various situations: forms, form gridviews, navigation, Office files, etc.

Consequently we find three types of display:

  • Display text (Office files, SMS, or any non-html media)

  • Display HTML (gridviews, navigation)

  • Control (forms, inline edition)

This article focuses on the latter, the other customizations are detailed here.

This topic contains the following sections:

Data example

We consider the following example, listing static apnea records with their respective duration.

Our objective is to split the duration control into minutes and seconds controls.

Records Inline Edition 1
HTML Helper

In order to change the control's look and feel, you need to open the HtmlHelper corresponding to your content-type ([Generated_Project]\Forms\Html Helpers\Record_Helper.cs).

Then override the GetControlHtml method like this:

C#
protected override string GetControlHtml(PFField field, PFFieldControlMode mode, object fieldValue)
{
    if (field.Name == Record.FieldName_Duration && mode != PFFieldControlMode.Hidden)
    {
        //Split minutes and seconds
        String[] parts = fieldValue.GetString().Split(':');
        int minutes = parts.First().GetInt();
        int seconds = parts.Length > 1 ? parts.Last().GetInt() : 0;

        //Get id of both inputs
        String fieldControlId1 = field.GetFieldControlId(CurrentObject.Id, "Minutes");
        String fieldControlId2 = field.GetFieldControlId(CurrentObject.Id, "Seconds");

        //Generate final HTML
        String control1 = String.Format("<input {0} value='{1}' class='PFControl' type='text' size='2' />",
            PFField.GetFieldControlProperties(fieldControlId1), minutes);
        String control2 = String.Format("<input {0} value='{1}' class='PFControl' type='text' size='2' />",
            PFField.GetFieldControlProperties(fieldControlId2), seconds);

        return String.Format("{0} min {1} s", control1, control2);
    }
    else
        return base.GetControlHtml(field, mode, fieldValue);
}

The use of the method GetFieldControlId is very important.

The first part of the id will indicate which field of the current item is involved. The second part (here named "Minutes" and "Seconds") identifies each generated control and their future posted value.

The method PFField.GetFieldControlProperties is just creating id and name HTML attributes from the specified id.

Binder

Because the list of controls (quantity and names) has been changed, the MVC binder must also be modified to handle the new posted values.

You will find it at [Generated_Project]\Forms\Item Binders\RecordItemBinder.cs.

With the following code, overriding the SaveFieldValues method:

C#
public override void SaveFieldValues(PFField field, Dictionary<string, object> controlsValues)
{
    if (field.Name == Record.FieldName_Duration)
    {
        int minutes = controlsValues["Minutes"].ToString().GetInt();
        int seconds = controlsValues["Seconds"].ToString().GetInt();
        Item.__Duration = String.Format("{0}:{1}", minutes, seconds.ToString("00"));
    }
    else
        base.SaveFieldValues(field, controlsValues);
}

The duration data is updated from the posted values. Notice the use of the dictionary with the keys "Minutes" and "Seconds" used earlier in the Html Helper.

Result

Once deployed, the control looks like this:

Records Inline Edition 2

And the binding is effective:

Records Inline Edition 3
Mixing fields

You can also override the control to include the control of another field.

Consider a new (choice) field "Nationality" (targeting the Country content-type) has been added to the content-type Record, and we want to be able to edit its value without changing the view.

First possibility, we can call the GetControlHtml for that field:

C#
protected override string GetControlHtml(PFField field, PFFieldControlMode mode, object fieldValue)
{
    if (mode != PFFieldControlMode.Hidden)
    {
        if (field.Name == Record.FieldName_Duration)
        {
            //...
        }
        else if (field.Name == Record.FieldName_Name)
        {
            //Get nationality field and value
            PFField nationalityField = CurrentObject.ParentContentType.PFField_Nationality;
            PFFieldChoiceValue nationality = CurrentObject.__Nationality;

            //Generate input HTML
            String fieldControlId1 = field.GetDefaultFieldControlName(CurrentObject.Id);
            String control1 = String.Format("<input {0} value='{1}' class='PFControl' type='text' />",
                PFField.GetFieldControlProperties(fieldControlId1), CurrentObject.__Name);
            String control2 = GetControlHtml(nationalityField, mode, nationality);

            return String.Format("{0}<br />{1}", control1, control2);
        }
    }

    return base.GetControlHtml(field, mode, fieldValue);
}

Notice the use of another method to get the control name "field.GetDefaultFieldControlName". This method returns the name commonly used by PACKflow, providing a big advantage: there is no need to override the binder this time.

It gives the following result:

Records Inline Edition 4

But you could also generate the selection control yourself:

C#
protected override string GetControlHtml(PFField field, PFFieldControlMode mode, object fieldValue)
{
    if (mode != PFFieldControlMode.Hidden)
    {
        if (field.Name == Record.FieldName_Duration)
        {
            //...
        }
        else if (field.Name == Record.FieldName_Name)
        {
            //Get nationality field and value
            PFField nationalityField = CurrentObject.ParentContentType.PFField_Nationality;
            PFFieldChoiceValue nationality = CurrentObject.__Nationality;
            long nationalityId = nationality.GetSelectedId();

            //Prepare nationality options
            List<String> countryOptions = new List<String>();
            foreach (Country country in CurrentObject.ParentApplication.ContentType_Country.Items.GetAll())
            {
                String selected = country.Id == nationalityId ? "selected" : "";
                countryOptions.AddFormat("<option value='{0}' {1}>{2}</option>",
                    country.GetReference(Country.FieldName_Name).ToString(), selected, country.__Name);
            }

            //Get id of both inputs
            String fieldControlId1 = field.GetDefaultFieldControlName(CurrentObject.Id);
            String fieldControlId2 = nationalityField.GetDefaultFieldControlName(CurrentObject.Id);

            //Generate final HTML
            String control1 = String.Format("<input {0} value='{1}' class='PFControl' type='text' />",
                PFField.GetFieldControlProperties(fieldControlId1), CurrentObject.__Name);
            String control2 = String.Format("<select {0} class='PFControl'>{1}</select>",
                PFField.GetFieldControlProperties(fieldControlId2), countryOptions.Concat(""));

            return String.Format("{0}<br />{1}", control1, control2);
        }
    }

    return base.GetControlHtml(field, mode, fieldValue);
}

Again, we used the "GetDefaultFieldControlName" method to get the control name, hence no need to override the binder.

Records Inline Edition 5