How to update a related Entity

Date: 01.11.2012

One of the new functionalities of MatchPoint 3.1 allows you to update also a related entity in the edit Form Web Part of the entity.

For this you need to create your own form field and a Model Configuration. In this example we have three tables in a database. The table "Staff" holds the employees, the table "Skill" contains the differnt skills throughout the company and the table "SkillProfile" represents which skills each staff holds and the according skill level.

In our example we will use a Form Web Part to edit a staff member and also the skills of this staff Member. To achieve this we need a custom control that displays and handles the skills of the staff member.

Creating the ModelConfiguration

You need to configure all three tables in a ModelConfiguration. The skill table will be used to display all available skills in a dropdown.

Figure 1: lightbox

Creating the FieldControl

To create a custom form field we need a at least three components:

  1. A class that inherits from FormDataField and represents the configuration entry for the custom form field.
  2. A class that inherits from FieldControl and represents the control on the server.
  3. A javscript file that creates the control on the client and handles the controls behavior.

Configuration Code

/// <summary>
/// Class who represents the configuration editor entry for the
/// SkillSetFieldControl
/// </summary>
[Serializable]
public class SkillSetField: FormDataField
{
    private const string columnSkillId = "SkillId";
    private const string columnSkillLevel = "SkillLevel";
    private const string columnName = "Name";
    private const string tableSkill = "Skill";
    private const string tableSkillProfile = "SkillProfile";

    private ModelConfiguration model;
    private ModelConfiguration Model
    {
        get
        {
            return model ?? (model = ModelConfiguration.GetByName(
                                               "CompanyModel"
                                              , MPInstance.Current));
        }
    }

    /// <summary>
    /// Overrides the method from FormDataField and returns the
    /// default values.
    /// We don't need a default value in this example. Therefore we 
    /// return an empty array.
    /// </summary>
    /// <returns>An empty array</returns>
    public override object GetDefaultValue()
    {
        return new object [0] ;
    }

    /// <summary>
    /// Overrides the method of FormDataField. 
    /// The method is needed to initialize the form field.
    /// </summary>
    /// <param name="item">IUpdatable item </param>
    /// <returns>An array of SkillProfile</returns>
    public override object GetValue(IUpdatable item)
    {
        return ((IEnumerable<EntityResultRow>) item[Name])
            .Select(r => new SkillProfile((int)r[columnSkillId],
                                         (int)r[columnSkillLevel]))
                .ToArray();            
    }

    /// <summary>
    /// This Method needs to be overwritten because we want to use the 
    /// field control to update also the SkillProfile entity.
    /// For every SkillProfile we create an EntityDBProxy representing 
    /// an entry in the SkillProfile table. The array of EntityDbProxy 
    /// is set in the IUpdatable and therefore the entity SkillProfile 
    /// will also be updated.
    /// </summary>
    /// <param name="item">The IUpdatable item </param>
    /// <param name="value">Array of SkillProfile</param>
    public override void SetValue(IUpdatable item, object value)
    {
        List<IUpdatable> proxies = new List<IUpdatable>();

        foreach(SkillProfile sp in (SkillProfile[])value)
        {
            EntityDbProxy proxy = new EntityDbProxy(
                                      Model.GetEntity(tableSkillProfile)
                                     , null
                                     , MPInstance.Current);

            proxy[columnSkillId] = sp.SkillId;
            proxy[columnSkillLevel] = sp.Level;
            proxies.Add(proxy);
        }
        item[Name] = proxies;
    }

    /// <summary>
    /// Creates the form control SkillSetFieldControl.
    /// </summary>
    /// <returns>The created SkillSetFieldControl</returns>
    public override Control CreateControl(FieldsForm form, bool readOnly)
    {
        SkillSetFieldControl ctrl = new SkillSetFieldControl();
        EntityResultRow[] rows = Model.GetEntity(tableSkill)
                                                .Rows.ToArray();
        ctrl.Skills = rows.Select(
                        r => new Skill((int)r[columnSkillId]
                                       , (string)r[columnName]))
                                     .ToArray();

        return ctrl;
    }
}


public class Skill
{
    public int SkillId;
    public string Name;

    public Skill()
    {}

    public Skill(int id, string name)
    {
        SkillId = id;
        Name = name;
    }
}


public class SkillProfile
{
    public int SkillId;
    public int Level;

    public SkillProfile()
    {}

    public SkillProfile(int skillId, int skillLevel)
    {
        SkillId = skillId;
        Level = skillLevel;
    }
}

Control Code

public class SkillSetFieldControl : FieldControl
{
    [JavaScriptBehaviorVariable(SyncMode.IncludeInCallback 
                                | SyncMode.IncludeInPostback)]
    public SkillProfile[] SkillProfiles = new SkillProfile[0];

    [JavaScriptBehaviorVariable]
    public string[] Levels = new string[] 
                                  {"Good", "Very Good", "Master"};

    [JavaScriptBehaviorVariable]
    public Skill[] Skills;

    public SkillSetFieldControl()
    {}

    public override object Value
    {
        get { return SkillProfiles; }
        set { SkillProfiles = (SkillProfile[]) value; }
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        JavaScriptBehaviorManager.RegisterBehavior(
                             this, typeof(SkillSetFieldControl));
    }

    /// <summary>
    /// Overrides the method Render of Control.
    /// Used to render the control directly to html.
    /// </summary>
    /// <param name="writer">Instance of HtmlTextWriter</param>
    protected override void Render(HtmlTextWriter writer)
    {
        writer.Write("<div id='{0}' class='skillControl'></div>"
                     , ClientID);            
    }
}

Client-side Code

$$.Namespace("Samples").SkillSetFieldControl = function () 
{
this.Setup = function ()
{
}

this.DataBind = function () 
{        
    var $ctrl = $(this.Control);

    this.$list = $("<div></div>");
    $ctrl.append(this.$list);

    // creates the html for representing the profiles
    for(var i = 0; i < this.SkillProfiles.length; i++)
    {
        this.$list.append(this.GetHtmlRow(this.SkillProfiles[i]));
    }

    var me = this;
    // creates a link to add a new skill profile
    var $button = $("<a>Add Row</a>");
    $ctrl.append($button);
    $button.click(function() { me.$list.append(me.GetHtmlRow()); });        
}

// in this function we set the client side profiles in the SkillProfiles
// before they're syncronizded.
this.OnBeforePostback = function ()
{
    var $selects = $(this.Control).find("select");

    this.SkillProfiles = new Array();

    for(var i = 0; i < $selects.length; i += 2)
    {
        this.SkillProfiles.push({SkillId: $($selects.get(i)).val()
                      , Level: $($selects.get(i + 1)).val()});
    }
}

// creates a dropdown representing all skills and selects the skill with
// the given skillId
this.GetSkillSelectBox = function (skillId)
{
    var html = new Array();

    // skill dropdown
    html.push("<select class='UserSelect'>");
    for(var i = 0; i < this.Skills.length; i++)
    {
          string forSelection = skillId 
            && skillId == this.Skills[i].SkillId ? " selected='1'" : "";
    html.push(
                $$.String.Format("<option value='{0}'{2}>{1}</option>"
                             , this.Skills[i].SkillId
                             , this.Skills[i].Name
                             , forSelection));
    }
    html.push("</select>");

    return $(html.join(""));    
}

// creates a dropbox representing all skill levels and
// selects the given level
this.GetLevelSelectBox = function (level)
{
    var html = new Array();

    // level dropdown
    html.push("<select>");
    for(var i = 0; i < this.Levels.length; i++)
    {
      html.push($$.String.Format("<option value='{0}'{2}>{1}</option>"
                              , i
                              , this.Levels[i]
                              , level == i ? " selected='1'" : ""));
    }
    html.push("</select>");

    return $(html.join(""));         
}

// for each skill profile three items will be created:
// (1) a dropbox representing the skill
// (2) a dropbox representing the skill level 
// (3) a link to delete the according skill profile
this.GetHtmlRow = function (sp)
{
    var $row = $("<div></div>");

    $row.append(this.GetSkillSelectBox(sp != null ? sp.SkillId : null));
    $row.append(this.GetLevelSelectBox(sp != null ? sp.Level : null));

    // removes the row with the according skill profile
    var $button = $("<a>Delete Row</a>");
    $button.click(function() { $(this).parent().remove(); });

    $row.append($button);

    return $row;
}
}

Configuring a Form Web Part

Add a Form Web Part to your Site and configure it with:

  1. a ModelDataAdapter that uses the Entity Staff and a RecordIdExpression as a reference to one record
  2. Multiple fields such as Firstname(TextField), Lastname (TextField), StaffToSkillProfile(SkillSetField)

Note: it's important to choose the name of the relation (that you configured in the Model Configuration) as the name of the SkillSetField.

Adding and Changing the Skills

Now you're able to add, remove and change the skills of an emplyee. Or if the form is empty you are also able to create a new employee with or without a skill profile.

Figure 2: lightbox

results matching ""

    No results matching ""