Expression Evaluation Time: ms / Round trip: ms
How to update a related Entity
01. Nov
2012
by Christina Category:
Technical
2

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.

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.

SkillSetFieldControl.cs

Samples.SkillSetFieldControl.js

ModelConfiguration.xml

Comments
lgroener
06.11.2012 12:25
It’s a very interesting and helpful post.
Could you please also provide the configuration.xml for the model?
Is it possible to work with a table with composite keys in a model configuration?
Another question: Which of these features are really new to MP 3.1? We have a similar problem at a customer with MP 3.0. So we should know if the update on MP 3.1 is necessary or not.
thx, Lucas
Christina
06.11.2012 01:52
Hello Lucas
Thanks for your questions and feedback.
I added the ModelConfiguration after the other files.
As to your questions:
a) You can't use composite keys. You need a primary key for defining a relation or for editing an entity
b) Editing and viewing one entity is also possible in MP 3.0. If you want to update more than one entity in a form you need MP 3.1
ABOUT

This blog is about technical and non-technical aspects of the product MatchPoint and other SharePoint topics.

If you would like to post an article or if you have an idea for a post, please contact us.

ARCHIVE
COMMENTS
Reto Jeger
04.10.2017 09:15
Hello Reiner,
Thanks for pointing out the missing ... | Goto Post
rganser
29.09.2017 09:56
Hi, I downloaded the ZIP-file for MatchPoint Versi... | Goto Post
Glenn De Block
26.07.2017 03:54
Oh, I see creator now uses an EvaluateAction.
Nice... | Goto Post
Glenn De Block
26.07.2017 02:41
We recently upgraded our version, are you guys sur... | Goto Post
Sandra Perz
09.09.2016 12:22
Thanks, that helps a lot. | Goto Post