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:
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.
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:
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.
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:
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.
Once deployed, the control looks like this:
And the binding is effective:
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:
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:
But you could also generate the selection control yourself:
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.