Thursday, June 12, 2008

Creating Custom Edit Web Part to update Metadata

Recently at customer side, I faced some difficult task, Customer had following requirements:

1. Customer Wanted Meta data properties called Business units (Departments), Project and Project Phases.
2. The project had looks up field for Business units and similarly project phases had projects has look up field.
3. Customer wanted cascading on the drop down list, like once user selected Business unit the project should be filtered on the basis of selection and similarly project phases should be filtered on projects.
4. All the values of these should come from ORACLE E-Business Application and not from any list in share point.

Off these I thought of using Data View Part and/Or BDC to connect to oracle database, but then I realized Cascading is not supported Out-of –box by MOSS 2007, so for customizing edit form I ran out of ideas’ , I googled I searched read every article I can to find solution for the Cascading drop down , the nearest I came to finding solution was on this blog http://datacogs.com/datablogs/archive/2007/08/26/641.aspx , they guy has created its own field for parent and child drop down list which works perfectly, but I didn’t suite me because this works on single level and I had 2 level of cascading combo , I tired to customize the fields but again run out of ideas. So I decided to think as ASP.NET developer which is my core strength, I could not believe I can be defeated easily by MOSS 2007 with this simple requirement.

Finally I decided to create my own web part, and place it on edit form,
But creating this required me to dig deep in MOSS 2007 object model; I am assuming all of you the basic science and anatomy of Web Parts so let’s start with the code:


Here are the control declaration parts in the code:

#region "Control Declaration"
private DropDownList _DrpBusinessUnit;
private DropDownList _DrpProjects;
private DropDownList _DrpProjectPhases;
private DropDownList _DrpContentType;
private PeopleEditor _TxtAuthorName;
private PeopleEditor _TxtForwardedBy;
private TextBox _TxtDocumentCreationDate;
private TextBox _TxtSummary;
private TextBox _TxtDocumentType;
private TextBox _TxtDoucmentCategory;
private TextBox _TxtDocumentSubject;
private TextBox _TxtRetentionPeriod;
private DropDownList _DrpWorkInProgress;
private TextBox _TxtArchiveLocation;
private TextBox _TxtSecurityLevel;
private PeopleEditor _TxtSecurityAdministartor;
private PeopleEditor _TxtAccessNotication;
private TextBox _TxtMediyaType;
private TextBox _TxtFileType;
private TextBox _TxtFilePath;
private TextBox _TxtDocumentExpiryDate;
private TextBox _TxtDocumentExpiryNotification;
private PeopleEditor _TxtPersonToBeNotified;
private TextBox _TxtRecievedFrom;
private Button _BtnSave;
private Button _BtnCancel;
private TextBox _TxtTitle;
#endregion

Notice People Editor in here, we will look in some what detail how to use it.

Next fire Protected override void CreateChildControls() ; here we will add object to web part container and add events for over drop downs , obviously I have stripped down the method , for full listing please see the attached source code


// create a new instance of drop down list
// give it ID
// Set AutoPost True
//attach new event handler
//add new control to web part

this._DrpBusinessUnit = new DropDownList();
this._DrpBusinessUnit.ID = "DrpBusinessUnit";
this._ DrpBusinessUnit.AutoPostBack = true;
this._ DrpBusinessUnit.SelectedIndexChanged += new EventHandler(DrpBusinessUnit_SelectedIndexChanged);
this.Controls.Add(_DrpBusinessUnit);


this._DrpProjects = new DropDownList();
this._DrpProjects.ID = "DrpProjectName";
this._ DrpProjects.AutoPostBack = true;
this._ DrpProjects.SelectedIndexChanged += new EventHandler(DrpProjects_SelectedIndexChanged);
this.Controls.Add(_DrpProjects);

this._DrpProjectPhases = new DropDownList();
this._DrpProjectPhases.ID = "DrpProjectPhases";
this._ DrpProjectPhases.AutoPostBack = true;
this._ DrpProjectPhases.SelectedIndexChanged += new EventHandler(DrpProjectPhases_SelectedIndexChanged);
this.Controls.Add(_DrpProjectPhases);

………………..

this._BtnSave = new Button();
this._BtnSave.ID = "BtnSave";
this._BtnSave.Text = "OK";
this._BtnSave.Click += new EventHandler(BtnSave_Click);
this._BtnSave.CssClass = "ms-ButtonHeightWidth";
this.Controls.Add(_BtnSave);


this._BtnCancel = new Button();
this._BtnCancel.ID = "BtnCancel";
this._BtnCancel.Text = "Cancel";
this._BtnCancel.CssClass = "ms-ButtonHeightWidth";
this.Controls.Add(_BtnCancel);


this._DrpContentType = new DropDownList();
this._DrpContentType.ID = "DrpContent Type";
this._DrpContentType.AutoPostBack = true;
this._DrpContentType.SelectedIndexChanged += new EventHandler(DrpContentType_SelectedIndexChanged);
this.Controls.Add(_DrpContentType);
Populate_ContentTypes();
// I had multiple contnent type we will see this method in detail
GetCurrent_Metadata();
// now get existing metadata if any from the list item or document library from moss 2007
Populate_BussinessInit()
// popluate Business unit combo from oracle


private void Populate_ContentTypes()
{
SPContentTypeCollection ContentTypes = SPContext.Current.List.ContentTypes;
// get all the content types associate with current list and store it in contnet collection
foreach (SPContentType spc in ContentTypes)
{
// iterate collection and add it in drop down for content types
_DrpContentType.Items.Add(spc.Name.ToString());
}

_DrpWorkInProgress.Items.Add("Draft");
_DrpWorkInProgress.Items.Add("Version");
_DrpWorkInProgress.Items.Add("Final");


}

public void GetCurrent_Metadata()
{
try
{


SPWeb MyWeb = SPContext.Current.Web;
// cut current web collection from the context of this web part in order to make it
// generic on list
SPList currentList = SPContext.Current.List;
// get the current list from the context
SPListItem item = (SPListItem)SPContext.Current.Item;
// get the current item from the current list context

foreach (Control cnt in this.Controls)
// Iterate each and every control in the web part that we added\
{
string CID = cnt.ID.ToString().Replace("Txt","").Replace("Drp","");
// now just in order to save time what i did , was set ID of each control
// as the same name of list column in order to bind them with prefix txt or drp
// we will remove it to get column name in the list




if (cnt.GetType().ToString() == "System.Web.UI.WebControls.TextBox")
{
// check if the control is type of text box
if (item[CID] != null)
// item is not null from the list columns
{
// create a tempbox and parse current control as textbox
TextBox temp = (TextBox)cnt;
temp.Text = item[CID].ToString();
// set value of this text box from value in the list item
}
}
else if (cnt.GetType().ToString() == "System.Web.UI.WebControls.DropDownList")
{ // if type of dropdownlist
if (item[CID] != null)
{ // item value is not null

// have to provide check at later stages.
try
{
// find the current item in t he drop down list and make it as selected
((DropDownList)cnt).Items.FindByText(item[CID].ToString()).Selected = true;
}
catch
{

}

}
}
else if (cnt.GetType().ToString() == "Microsoft.SharePoint.WebControls.PeopleEditor")
{ // if its people editior
if (item[CID] != null)
{
// get the comma sperate values from the item list
//people editor store the account names in this format
// 1;#Account;45;#AccountName
// where intengers are the Sharepoint user ID that MOSS 2007 gives it to each object
// in active directory
string strCommaSperatedValues = item[CID].ToString().Replace("#", "").ToString();
string[] strAccounts = strCommaSperatedValues.Split(';');
// split it
strCommaSperatedValues = "";
foreach (string strAccount in strAccounts)
{ // itreate it and check which one's are email id's
if (strAccount.Contains("@") == true)
{
strCommaSperatedValues += strAccount + " ; ";
// store in temp varialbe
}
}
((PeopleEditor)cnt).CommaSeparatedAccounts = strCommaSperatedValues ;
// assign the string to people editor
((PeopleEditor)cnt).Validate();
// this method validates the entry in people editor just like CTRL + K , but some time even with
//valid entry it was not working not sure what the problem was

}


}

}


// smilary with author people editior
_TxtAuthorName.CommaSeparatedAccounts = item["AuthorName"].ToString().Replace("#", "").Replace("1", "").Replace(";","");
_TxtForwardedBy.CommaSeparatedAccounts = item["ForwardedBy"].ToString().Replace("#", "").Replace("1", "").Replace(";", "");


}

catch (Exception ex)
{

}

}




private void Populate_BussinessInit
{

/*
declare connection object of oracle client
* create adatpter
* create dataset
* populate dataset by calling fill method
* populate combo
* this part i am working on it but should not be trouble its simple asp.net code

* /
}


public void Update_MetaData()
{

SPWeb MyWeb = SPContext.Current.Web;
// get current website in collection
SPList currentList = SPContext.Current.List;
// get current list
SPListItem item = (SPListItem)SPContext.Current.Item;
// get current item
MyWeb.AllowUnsafeUpdates = true;
// you have to allow website to accept unsafe updates , becuase by default MOSS 2007
// does not allow programmatic updates to the content types
foreach (Control cnt in this.Controls)
{
// similarly with get methods iterate each controls
//basically this is reverel now from text box we will set item properties
string CID = cnt.ID.ToString().Replace("Txt", "").Replace("Drp", "");
if (cnt.GetType().ToString() == "System.Web.UI.WebControls.TextBox")
{
if (((TextBox)cnt).Text.Length !=0)
item[CID] = ((TextBox)cnt).Text;

}
else if (cnt.GetType().ToString() == "System.Web.UI.WebControls.DropDownList")
{

if (((DropDownList)cnt).SelectedIndex != -1)
{
item[CID] = ((DropDownList)cnt).SelectedItem.Text;
}

}
else if (cnt.GetType().ToString() == "Microsoft.SharePoint.WebControls.PeopleEditor")
{
// Logic explained bellow
}


}
PickerEntity objEntity = (PickerEntity)_TxtAuthorName.ResolvedEntities[0];
// get the first resolved entry from author name people picker as it does not allow multiple values
//parse it as Picker entirty
int UserID = int.Parse(objEntity.EntityData["SPUserID"].ToString());
// get the sharepoint User ID of the selected user

SPFieldUserValue userValue = new SPFieldUserValue(MyWeb, UserID ,objEntity.DisplayText );
// now this is really intersting in order to save people and group you must declare
//SPFieldUserValue class and instantation it with MOSS 2007 userID and Name

item["AuthorName"] = userValue;
// set it authorname columns

item.Update();
// update the items
MyWeb.Update();
//update the web
MyWeb.AllowUnsafeUpdates = false;
// set allowance unsafe update to false again just in case



}

Now rest of the logic is simple ASP.Net fire selected index event of business unit to get the data from oracle stored procedure for project and from project selected index get project phases , add validation what ever you want just like ASP.NET web page. Render the web part they way you want.With the power of ASP.NET behind your code possibles are unlimited now , I just demonstrated to you how to get and update metadata from list.

And Remember to add system.data.oracleclient assembly to full turst level in GAC and MOSS 2007 safe assembly tags in web.config , otherwise you will get security permission error in MOSS 2007.

The web part code is still incomplete but purpose of this post was to demonstrate idea how to do things programmatic ally.

If you need further help do not hesitate to write me at kaisar.wadiwala@gmail.com


Kind Regards ,

Muhammad Kaisar Wadiwala
Microsoft Certified Technology Specialist