Monday, April 9, 2012

How to show related item on InfoPath task form

Task form is displayed using WrkTaskIP.aspx page from TEMPLATE\LAYOUTS folder.

  1. Create copy of WrkTaskIP.aspx into your project named CustomWrkTaskIP.aspx.
  2. Register forms.css style:
    <asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
    <SharePoint:CssRegistration runat="server" Name="forms.css" EnableCssTheming="true" />
    </asp:Content>

  3. Add the following code into page:
    <div>
    <h3>Related item</h3>
    <table width="100%" class="ms-formtable" border="0" cellspacing="0" cellpadding="0">
    <SharePoint:ListFieldIterator ID="ItemFieldIterator" runat="server" ControlMode="Display" />
    </table>
    </div>

  4. Add codebehind to your page:
    public class CustomWrkTaskPage : WrkTaskIPPage
    {
    protected ListFieldIterator ItemFieldIterator;

    protected override void OnLoad(EventArgs ea)
    {
    base.OnLoad(ea);

    Guid workflowInstanceId = new Guid((string)m_task[SPBuiltInFieldId.WorkflowInstanceID]);
    SPWorkflow workflow = new SPWorkflow(Web, workflowInstanceId);


    ItemFieldIterator.ListId = workflow.ParentList.ID;
    ItemFieldIterator.ItemId = workflow.ParentItem.ID;
    }
    }

  5. Set DisplayFormUrl and EditFormUrl properties of all task content types to your CustomWrkTaskIP.aspx:
    foreach (SPContentType taskContentType in taskList.ContentTypes)
    {
    if (!taskContentType.Id.IsChildOf(SPBuiltInContentTypeId.WorkflowTask))
    continue;

    taskContentType.DisplayFormUrl = "_layouts/My/CustomWrkTaskIP.aspx";
    taskContentType.EditFormUrl = "_layouts/My/CustomWrkTaskIP.aspx";
    taskContentType.Update();
    }

Friday, April 6, 2012

Reserved words in SharePoint url

Avoid using these words in any SharePoint url (file name, folder name, site name etc.):

CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9

You can create folder named “CON”, but when you go to folder url, you get 404 error.

Thanks to the following articles:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
http://spcz.blogspot.com/2012/03/sharepoint-reserved-words.html

Monday, January 23, 2012

Filtered lookup – jquery and client object model

I’ve created filtered lookup based on extended version of cascaded lookup from blog.libinuko.com. It’s more general and it supports both versions of lookup field (dropdown / autocomplete).

Thanks to the following scripts:
http://www.sharepointboris.net/js/spcd
http://blog.libinuko.com/2011/01/29/sharepoint-2010-how-to-create-cascading-lookup-field-using-client-object-model

Reference jquery.js and SP.js from your page.

<SharePoint:ScriptLink Name="SP.js" runat="server" OnDemand="true" Localizable="false" />

Javascript code for filtered lookup:
// Filtered lookup

function FilteredLookup(fieldTitle, listName, query) {
// Set variables
this.fieldTitle = fieldTitle;
this.listName = listName;

// Load filtered items for target lookup field
var clientCtx = new SP.ClientContext.get_current();
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml("<View><Query>" + query + "</Query></View>");

var list = clientCtx.get_web().get_lists().getByTitle(listName);
this.listItems = list.getItems(camlQuery);
clientCtx.load(this.listItems, "Include(Id, Title)");
clientCtx.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
}

FilteredLookup.prototype.onQuerySucceeded = function (sender, args) {
// Find lookup field
var lookup = $("select[title='" + this.fieldTitle + "']"); // 0-19 items
if (lookup.length == 0) {
// Bind filtered items to lookup field (input / autocomplete)
lookup = $("input[title='" + this.fieldTitle + "']"); // > 20 items
if (lookup.length == 0)
return;
var lookupHidden = $("input[id='" + lookup[0].optHid + "']");
if (lookupHidden.length == 0)
return;

var choices = "";
var listItemEnumerator = this.listItems.getEnumerator();
while (listItemEnumerator.moveNext()) {
var listItem = listItemEnumerator.get_current();
if (choices.length > 0)
choices += "|";
choices += listItem.get_item("Title") + "|" + listItem.get_id();
}
lookup.attr("choices", choices);
}
else {
// Bind filtered items to lookup field (select / option)
var options = "";
var currentValue = lookup.val();
var listItemEnumerator = this.listItems.getEnumerator();
while (listItemEnumerator.moveNext()) {
var listItem = listItemEnumerator.get_current();
options += '<option value="' + listItem.get_id() + '">' + listItem.get_item('Title') + '</option>';
}
lookup.html(options);
lookup.val(currentValue);
}
}

FilteredLookup.prototype.onQueryFailed = function (sender, args) {
alert("Request failed. " + args.get_message() + "\n" + args.get_stackTrace());
}

Sample:
var filterCountry;

function setupLookups() {
filterCountry = new FilteredLookup("Country", "Countries", "<Where><BeginsWith><FieldRef Name='Title' /><Value Type='Text'>A</Value></BeginsWith></Where>");
}

// Attach function on document ready
$(document).ready(
function () {
ExecuteOrDelayUntilScriptLoaded(setupLookups, "SP.js");
});

Friday, January 13, 2012

How to initialize PeopleEditor control?

You can use CommaSeparatedAccounts property for users. But this property doesn’t work for groups.

SPGroup
PeopleEditor peopleEditor = ...;
SPGroup group = ...;
PickerEntity entity = new PickerEntity
{
Key = group.LoginName,
DisplayText = group.Name,
Description = group.Name,
IsResolved = true
};
entity.EntityData.Add("AccountName", group.LoginName);
entity.EntityData.Add("SPGroupID", group.ID.ToString());
entity.EntityData.Add("PrincipalType", "SharePointGroup");
peopleEditor.Add(entity);


SPUser

PeopleEditor peopleEditor = ...;
SPUser user = ...;
PickerEntity entity = new PickerEntity
{
Key = user.LoginName,
DisplayText = user.Name,
Description = user.Name,
IsResolved = true
};
entity.EntityData.Add("AccountName", user.LoginName);
entity.EntityData.Add("SPUserID", user.ID.ToString());
if (user.IsDomainGroup)
entity.EntityData.Add("PrincipalType", "SecurityGroup");
else
entity.EntityData.Add("PrincipalType", "User");
peopleEditor.Add(entity);

Wednesday, January 11, 2012

How to handle version conflict during Update operation

You can use Microsoft.Office.Server.Utilities.EventReceiverUtility class and IsVersionConflictException method. You can find it useful when you use SPWeb.AllProperties for some application counter for example.

SPWeb web = ...;
bool retry = true;
int count = 0;
while (retry)
{
try
{
int lastId = 0
if (web.AllProperties.ContainsKey("LastId"))
lastId = (int)web.GetProperty("LastId");
else
web.AddProperty("LastId", lastId);

lastId++;
web.SetProperty("LastId", lastId);
retry = false;
}
catch (Exception ex)
{
if (!EventReceiverUtility.IsVersionConflictException(ex))
throw;
if (count > 2)
throw;
}
count++;
}

Monday, January 2, 2012

Cascaded lookup – jquery and client object model

I’ve extended version from blog.libinuko.com. It’s more general and it supports both versions of lookup field (dropdown / autocomplete).
Thanks to the following scripts:
http://www.sharepointboris.net/js/spcd
http://blog.libinuko.com/2011/01/29/sharepoint-2010-how-to-create-cascading-lookup-field-using-client-object-model
Reference jquery.js and SP.js from your page.

<SharePoint:ScriptLink Name="SP.js" runat="server" OnDemand="true" Localizable="false" />

Javascript code for cascaded lookup:
// Cascaded lookup

function CascadedLookup(sourceFieldTitle, targetFieldTitle, targetListName, targetFieldInternalName) {
// Set variables
this.sourceFieldTitle = sourceFieldTitle;
this.targetFieldTitle = targetFieldTitle;
this.targetListName = targetListName;
this.targetFieldInternalName = targetFieldInternalName;
this.lastValue = null;

// Find source lookup field
var lookup = $("select[title='" + sourceFieldTitle + "']"); // 0-19 items
if (lookup.length == 0) {
lookup = $("input[title='" + sourceFieldTitle + "']"); // > 20 items
}

// Attach event handler to source lookup field
if (lookup.length != 0) {
var self = this;
if (lookup[0].optHid) {
// Input field saves value to hidden field
$("input[id='" + lookup[0].optHid + "']").bind("propertychange", function () { self.updateLookup(); });
this.autocomplete = true;
} else {
// Standard dropdown (select / option)
lookup.change(function () { self.updateLookup() });
this.autocomplete = false;
}

// Filter for current values
this.updateLookup();
}
}

CascadedLookup.prototype.updateLookup = function () {
// Get current value of source lookup field
var selectedValue;
if (this.autocomplete) {
var lookup = $("input[title='" + this.sourceFieldTitle + "']");
if (lookup.length == 0)
return;
var lookupHidden = $("input[id='" + lookup[0].optHid + "']");
if (lookupHidden.length == 0)
return;
selectedValue = lookupHidden.val();
}
else {
var lookup = $("select[title='" + this.sourceFieldTitle + "'] option:selected");
if (lookup.length == 0)
return;
selectedValue = lookup.val();
}

// Don't refresh when value didn't change
if (this.lastValue != null && this.lastValue == selectedValue)
return;
this.lastValue = selectedValue;

// Load filtered items for target lookup field
var clientCtx = new SP.ClientContext.get_current();
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml("<View><Query><Where><Eq>" +
"<FieldRef Name='" + this.targetFieldInternalName + "' LookupId='TRUE' />'" +
"<Value Type='Lookup'>" + selectedValue + "</Value>" +
"</Eq></Where></Query></View>");

var list = clientCtx.get_web().get_lists().getByTitle(this.targetListName);
this.listItems = list.getItems(camlQuery);
clientCtx.load(this.listItems, "Include(Id, Title)");
clientCtx.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
}

CascadedLookup.prototype.onQuerySucceeded = function (sender, args) {
// Find target lookup field
var lookup = $("select[title='" + this.targetFieldTitle + "']"); // 0-19 items
if (lookup.length == 0) {
var lookup = $("input[title='" + this.targetFieldTitle + "']"); // > 20 items
if (lookup.length == 0)
return;
var lookupHidden = $("input[id='" + lookup[0].optHid + "']");
if (lookupHidden.length == 0)
return;

// Bind filtered items to target lookup field (input / autocomplete)
var choices = "";
var currentValue = lookupHidden.val();
var currentValueFound = false;
var listItemEnumerator = this.listItems.getEnumerator();
while (listItemEnumerator.moveNext()) {
var listItem = listItemEnumerator.get_current();
if (choices.length > 0)
choices += "|";
var id = listItem.get_id();
if (currentValue == id)
currentValueFound = true;
choices += listItem.get_item("Title") + "|" + id;
}
lookup.attr("choices", choices);
if (!currentValueFound) {
lookup.val("");
lookupHidden.val("");
}
}
else {
// Bind filtered items to target lookup field (select / option)
var options = "";
var currentValue = lookup.val();
var listItemEnumerator = this.listItems.getEnumerator();
while (listItemEnumerator.moveNext()) {
var listItem = listItemEnumerator.get_current();
options += '<option value="' + listItem.get_id() + '">' + listItem.get_item('Title') + '</option>';
}
lookup.html(options);
lookup.val(currentValue);
}
}

CascadedLookup.prototype.onQueryFailed = function (sender, args) {
alert("Request failed. " + args.get_message() + "\n" + args.get_stackTrace());
}

Sample:

var cascadedCountryCity;

function setupLookups() {
cascadedCountryCity = new CascadedLookup("Country", "City", "Cities", "CountryLookup");
}

// Attach function on document ready
$(document).ready(
function () {
ExecuteOrDelayUntilScriptLoaded(setupLookups, "SP.js");
});

Thursday, December 29, 2011

Workflow related content field contains old item title

We have the following workflow:

  1. User enters new item without Title and workflow starts
  2. New Title is built and set in workflow
  3. Task is created

image

Related content field in task should contain new Title but it contains the empty one. You can create empty workflow action which has [PersistOnClose] attribute which commits all pending changes but it doesn’t help in this case. So we have created custom event receiver for Tasks list. It solves the problem.

public override void ItemAdding(SPItemEventProperties properties)
{
try
{
string listIdText = properties.AfterProperties["WorkflowListId"] as string;
string itemIdText = properties.AfterProperties["WorkflowItemId"] as string;
string workflowLink = properties.AfterProperties["WorkflowLink"] as string;
if (!string.IsNullOrEmpty(listIdText) &&
!string.IsNullOrEmpty(itemIdText) &&
!string.IsNullOrEmpty(workflowLink))
{
Guid listId = new Guid(listIdText);
int itemId = Convert.ToInt32(itemIdText);

SPList list = properties.Web.Lists[listId];
SPListItem item = list.GetItemById(itemId);

SPFieldUrlValue url = new SPFieldUrlValue(workflowLink);
url.Description = item.Title;

properties.AfterProperties.ChangedProperties.Add("WorkflowLink", url.ToString());
}
}
catch (Exception ex)
{
Trace.WriteLine("WorkflowTaskEventReceiver.ItemAdding: " + ex.Message);
}
base.ItemAdding(properties);
}


Links:
About workflow batches and commit pending changes action